46

I'm using Node.js with express and already know the existence of response.redirect().

However, I'm looking for more of a forward() functionality similar to java that takes the same parameters as redirect, but internally forwards the request instead of having the client perform the redirect.

To clarify, I am not doing a proxy to a different server. I'd like to forward('/other/path') directly within the same app instance

It wasn't apparently obvious how to do this from the express documentation. Any help?

hong4rc
  • 3,999
  • 4
  • 21
  • 40
user1328174
  • 533
  • 1
  • 4
  • 7
  • possibly related: http://stackoverflow.com/questions/7967037/how-to-make-external-http-requests-with-node-js – Paul Aug 08 '13 at 21:27
  • to clarify, I am not doing a proxy to a different server. I'd like to forward('/other/path') directly within the same app instance – user1328174 Aug 08 '13 at 21:35

7 Answers7

26

You just need to invoke the corresponding route handler function.

Option 1: route multiple paths to the same handler function

function getDogs(req, res, next) {
  //...
}}
app.get('/dogs', getDogs);
app.get('/canines', getDogs);

Option 2: Invoke a separate handler function manually/conditionally

app.get('/canines', function (req, res, next) {
   if (something) {
      //process one way
   } else {
      //do a manual "forward"
      getDogs(req, res, next);
   }
});

Option 3: call next('route')

If you carefully order your router patterns, you can call next('route'), which may achieve what you want. It basically says to express 'keep moving on down the router pattern list', instead of a call to next(), which says to express 'move down the middleware list (past the router)`.

Peter Lyons
  • 142,938
  • 30
  • 279
  • 274
  • 1
    Let's assume the use case as follows: User logs in, but the app redirects them to a login page while saving the url they were intending to go to. After they log in, I retrieve the original url they intended to go to and want to "forward" them there. I know technically, it is better to redirect, but for the sake of argument i don't want them to make an extra request. How would any of the options above solve that? In essence, I feel like I need a way to retrieve the mapping of urls to handlers in express and somehow write my own forward function based on it. – user1328174 Aug 08 '13 at 23:09
22

You can implement forward (aka rewrite) functionality by changing request url property and calling next('route').

Note that the handler performing forward needs to be configured before other routes which you perform forwards to.

This is example of forwarding all *.html documents to routes without .html extension (suffix).

function forwards(req, res, next) {
    if (/(?:.+?)\.html$/.test(req.url)) {
        req.url = req.url.replace(/\.html$/, '');
    }
    next('route');
}

You call next('route') as the last operation. The next('route') passes control to subsequent routes.

As mentioned above, you need to configure forwards handler as one of the first handlers.

app.get('*', forwards);
// ...
app.get('/someroute', handler);

The above example will return the same content for /someroute as well as /someroute.html. You could also provide an object with a set of forward rules ({ '/path1': '/newpath1', '/path2': '/newpath2' }) and use them in forward mechanism.

Note that regular expression used in forwards function is simplified for mechanism presentation purposes. You would need to extend it (or perform check on req.path) if you would like to use querystring parameters etc.

I hope that will help.

Tom
  • 26,212
  • 21
  • 100
  • 111
16

For Express 4+

Using the next function does not work if the next handler is not added in the right order. Instead of using next, I use the router to register the handlers and call

app.get("/a/path", function(req, res){
    req.url = "/another/path";
    app.handle(req, res);
}

Or for HTML5 mode of React/Angular

const dir = process.env.DIR || './build';

// Configure http server
let app = express();
app.use('/', express.static(dir));

// This route sends a 404 when looking for a missing file (ie a URL with a dot in it)
app.all('/*\.*', function (req, res) {
    res.status(404).send('404 Not found');
});

// This route deals enables HTML5Mode by forwarding "missing" links to the index.html
app.all('/**', function (req, res) {
    req.url = 'index.html';
    app.handle(req, res);
});
Loren
  • 9,783
  • 4
  • 39
  • 49
12

Using the next function does not work if the next handler is not added in the right order. Instead of using next, I use the router to register the handlers and call

router.get("/a/path", function(req, res){
    req.url = "/another/path";
    router.handle(req, res);
}
Jeffrey Bosboom
  • 13,313
  • 16
  • 79
  • 92
Quoc Quach
  • 141
  • 1
  • 2
  • 3
    router is deprecated on Express 4+ – Manny Dec 01 '17 at 02:44
  • 3
    instead of router.get and router.handle, I use app.get and app.handle in Express 4+ and it works like a charm! Saved me! This should be marked as the right answer because it's the only right answer here. – lwdthe1 Feb 04 '18 at 20:51
  • @Manny I'm confused `let router = require('express').Router();` is deprecated in Express 4+? – Mike Lippert Sep 14 '18 at 18:30
  • 2
    **For Express 4+** instead of using `router.handle` use `req.app.handle` – Madacol Sep 26 '19 at 23:01
4

Express 4+ with nested routers

Instead of having to use the outside of route/function app, you can use req.app.handle

"use strict";

const express = require("express");
const app = express();

//
//  Nested Router 1
//
const routerOne = express.Router();

//  /one/base
routerOne.get("/base", function (req, res, next) {
  res.send("/one/base");
});

//  This routes to same router (uses same req.baseUrl)
//  /one/redirect-within-router -> /one/base
routerOne.get("/redirect-within-router", function (req, res, next) {
  req.url = "/base";
  next();
});

//  This routes to same router (uses same req.baseUrl)
//  /one/redirect-not-found -> /one/two/base (404: Not Found)
routerOne.get("/redirect-not-found", function (req, res, next) {
  req.url = "/two/base";
  next();
});

//  Using the full URL
//  /one/redirect-within-app -> /two/base
routerOne.get("/redirect-within-app", function (req, res, next) {
  req.url = "/two/base";

  // same as req.url = "/one/base";
  //req.url = req.baseUrl + "/base";

  req.app.handle(req, res);
});

//  Using the full URL
//  /one/redirect-app-base -> /base
routerOne.get("/redirect-app-base", function (req, res, next) {
  req.url = "/base";
  req.app.handle(req, res);
});



//
//  Nested Router 2
//
const routerTwo = express.Router();

//  /two/base
routerTwo.get("/base", function (req, res, next) {
  res.send("/two/base");
});

//  /base
app.get("/base", function (req, res, next) {
  res.send("/base");
});


//
//  Mount Routers
//
app.use("/one/", routerOne);
app.use("/two/", routerTwo);


//  404: Not found
app.all("*", function (req, res, next) {
  res.status(404).send("404: Not Found");
});
Eugene Song
  • 543
  • 6
  • 8
  • Next doesn't work like that. The next function simply sends the request to the next handler... –  Jul 15 '19 at 19:43
  • From the [Express 4 documentation](http://expressjs.com/en/4x/api.html#req.originalUrl): _"(req.originalUrl) This property is much like req.url; however, it retains the original request URL, allowing you to rewrite req.url freely for internal routing purposes."_ – Eugene Song Dec 20 '19 at 22:07
2
app.get('/menzi', function (req, res, next) {
        console.log('menzi2');
        req.url = '/menzi/html/menzi.html';
        // res.redirect('/menzi/html/menzi.html');
        next();
});

This is my code:when user enter "/menzi",the server will give the page /menzi/html/menzi.html to user, but the url in the browser will not change;

Yubq
  • 21
  • 1
1

You can use run-middleware module exactly for that. Just run the handler you want by using the URL & method & data.

https://www.npmjs.com/package/run-middleware

For example:

app.runMiddleware('/get-user/20',function(code,body,headers){
    res.status(code).send(body)
})
Aminadav Glickshtein
  • 23,232
  • 12
  • 77
  • 117