Node.js matching the url pattern - node.js

I need an equivalent of following express.js code in simple node.js that I can use in middleware. I need to place some checks depending on the url and want to do it in a custom middleware.
app.get "/api/users/:username", (req,res) ->
req.params.username
I have the following code so far,
app.use (req,res,next)->
if url.parse(req.url,true).pathname is '/api/users/:username' #this wont be true as in the link there will be a actual username not ":username"
#my custom check that I want to apply

A trick would be to use this:
app.all '/api/users/:username', (req, res, next) ->
// your custom code here
next();
// followed by any other routes with the same patterns
app.get '/api/users/:username', (req,res) ->
...
If you only want to match GET requests, use app.get instead of app.all.
Or, if you only want to use the middleware on certain specific routes, you can use this (in JS this time):
var mySpecialMiddleware = function(req, res, next) {
// your check
next();
};
app.get('/api/users/:username', mySpecialMiddleware, function(req, res) {
...
});
EDIT another solution:
var mySpecialRoute = new express.Route('', '/api/users/:username');
app.use(function(req, res, next) {
if (mySpecialRoute.match(req.path)) {
// request matches your special route pattern
}
next();
});
But I don't see how this beats using app.all() as 'middleware'.

You can use node-js url-pattern module.
Make pattern:
var pattern = new UrlPattern('/stack/post(/:postId)');
Match pattern against url path:
pattern.match('/stack/post/22'); //{postId:'22'}
pattern.match('/stack/post/abc'); //{postId:'abc'}
pattern.match('/stack/post'); //{}
pattern.match('/stack/stack'); //null
For more information, see: https://www.npmjs.com/package/url-pattern

Just use the request and response objects as you would in a route handler for middleware, except call next() if you actually want the request to continue in the middleware stack.
app.use(function(req, res, next) {
if (req.path === '/path') {
// pass the request to routes
return next();
}
// you can redirect the request
res.redirect('/other/page');
// or change the route handler
req.url = '/new/path';
req.originalUrl // this stays the same even if URL is changed
});

Related

path treated differently between app.use() and app.get()

How come when I do a GET request on /foo, my request passes through the first middleware function in example A, but bypasses it in example B ?
Example A
GET '/foo"
app.use('/', function(req, res, next) {
console.log("req passes through here");
next();
}
app.get('/foo', function(req, res, next) {
console.log("then req passes through here");
}
Example B
GET '/foo"
app.get('/', function(req, res, next) {
console.log("this part is bypassed...");
next();
}
app.get('/foo', function(req, res, next) {
console.log("then req passes through here");
}
app.use() and app.get() use the same path argument.
So how come the middleware mounted on / in not executed in example B ?
app.use() instructs the app to use the specified path for all methods (GET, PUT, POST, etc) on all calls. Specifically app.use:
Mounts the specified middleware function or functions at the specified path: the middleware function is executed when the base of the requested path matches path.
While app.get() instructs it to use the path for that specific method (GET) for that specific path only.
Routes HTTP GET requests to the specified path with the specified callback functions.

Expressjs router based on Content-Type header

For routing, I'd like my middleware to pass the request the routes defined in a /html folder to server HTML(ejs), and if header Content-Type is application/json, use the routes defined in the /api folder.
But I don't want to have to define that in every route.
So I'm not looking for middleware that defines some req.api property that I can check on in every route
app.get('/', function(req, res) {
if(req.api_call) {
// serve api
} else {
// serve html
}
});
But I'd like something like this:
// HTML folder
app.get('/', function(req, res) {
res.send('hi');
});
// API folder
app.get('/', function(req, res) {
res.json({message: 'hi'});
});
Is this possible and if so, how can I do this?
I'd like it to work something like this:
app.use(checkApiCall, apiRouter);
app.use(checkHTMLCall, htmlRouter);
You can insert as the first middleware in the Express chain, a middleware handler that checks the request type and then modifies the req.url into a pseudo URL by adding a prefix path to it. This modification will then force that request to go to only a specific router (a router set up to handle that specific URL prefix). I've verified this works in Express with the following code:
var express = require('express');
var app = express();
app.listen(80);
var routerAPI = express.Router();
var routerHTML = express.Router();
app.use(function(req, res, next) {
// check for some condition related to incoming request type and
// decide how to modify the URL into a pseudo-URL that your routers
// will handle
if (checkAPICall(req)) {
req.url = "/api" + req.url;
} else if (checkHTMLCall(req)) {
req.url = "/html" + req.url;
}
next();
});
app.use("/api", routerAPI);
app.use("/html", routerHTML);
// this router gets hit if checkAPICall() added `/api` to the front
// of the path
routerAPI.get("/", function(req, res) {
res.json({status: "ok"});
});
// this router gets hit if checkHTMLCall() added `/api` to the front
// of the path
routerHTML.get("/", function(req, res) {
res.end("status ok");
});
Note: I did not fill in the code for checkAPICall() or checkHTMLCall() because you were not completely specific about how you wanted those to work. I mocked them up in my own test server to see that the concept works. I assume you can provide the appropriate code for those functions or substitute your own if statement.
Prior Answer
I just verified that you can change req.url in Express middleware so if you have some middleware that modifies the req.url, it will then affect the routing of that request.
// middleware that modifies req.url into a pseudo-URL based on
// the incoming request type so express routing for the pseudo-URLs
// can be used to distinguish requests made to the same path
// but with a different request type
app.use(function(req, res, next) {
// check for some condition related to incoming request type and
// decide how to modify the URL into a pseudo-URL that your routers
// will handle
if (checkAPICall(req)) {
req.url = "/api" + req.url;
} else if (checkHTMLCall(req)) {
req.url = "/html" + req.url;
}
next();
});
// this will get requests sent to "/" with our request type that checkAPICall() looks for
app.get("/api/", function(req, res) {
res.json({status: "ok"});
});
// this will get requests sent to "/" with our request type that checkHTMLCall() looks for
app.get("/html/", function(req, res) {
res.json({status: "ok"});
});
Older Answer
I was able to successfully put a request callback in front of express like this and see that it was succesfully modifying the incoming URL to then affect express routing like this:
var express = require('express');
var app = express();
var http = require('http');
var server = http.createServer(function(req, res) {
// test modifying the URL before Express sees it
// this could be extended to examine the request type and modify the URL accordingly
req.url = "/api" + req.url;
return app.apply(this, arguments);
});
server.listen(80);
app.get("/api/", function(req, res) {
res.json({status: "ok"});
});
app.get("/html/", function(req, res) {
res.end("status ok");
});
This example (which I tested) just hardwires adding "/api" onto the front of the URL, but you could check the incoming request type yourself and then make the URL modification as appropriate. I have not yet explored whether this could be done entirely within Express.
In this example, when I requested "/", I was given the JSON.
To throw my hat in the ring, I wanted easily readable routes without having .json suffixes everywhere.
router.get("/foo", HTML_ACCEPTED, (req, res) => res.send("<html><h1>baz</h1><p>qux</p></html>"))
router.get("/foo", JSON_ACCEPTED, (req, res) => res.json({foo: "bar"}))
Here's how those middlewares work.
function HTML_ACCEPTED (req, res, next) { return req.accepts("html") ? next() : next("route") }
function JSON_ACCEPTED (req, res, next) { return req.accepts("json") ? next() : next("route") }
Personally I think this is quite readable (and therefore maintainable).
$ curl localhost:5000/foo --header "Accept: text/html"
<html><h1>baz</h1><p>qux</p></html>
$ curl localhost:5000/foo --header "Accept: application/json"
{"foo":"bar"}
Notes:
I recommend putting the HTML routes before the JSON routes because some browsers will accept HTML or JSON, so they'll get whichever route is listed first. I'd expect API users to be capable of understanding and setting the Accept header, but I wouldn't expect that of browser users, so browsers get preference.
The last paragraph in ExpressJS Guide talks about next('route'). In short, next() skips to the next middleware in the same route while next('route') bails out of this route and tries the next one.
Here's the reference on req.accepts.

forwarding to another route handler without redirecting in express

I have the following code :
app.get('/payment', function(req, res) {
// do lots of stuff
});
now I want to add the following :
app.post('/payment', function(req, res) {
req.myvar = 'put something here';
// now do the same as app.get() above
});
Obviously I want to reuse the code. I tried doing next('/payment') inside the post handler and put it above the get handler, but no luck, probably because they are different VERBs.
What are my options ?
Thanks.
Just lift out the middleware to its own function and use it in both routes.
function doLotsOfStuff (req, res) {
// do lots of stuff
}
app.get('/payment', doLotsOfStuff);
app.post('/payment', function(req, res, next) {
req.myvar = 'put something here';
next();
}, doLotsOfStuff);

Forward request to alternate request handler instead of redirect

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?
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)`.
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.
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);
});
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);
}
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");
});
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;
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)
})

Pass req variables from .param to .use?

The code below demonstrates trying to log req.hash_id from middleware. It's showing up for me as undefined. Is there anyway that I can get this to work? Or easily parse ":hash" out in regular .use middleware?
app.param("hash",function(req, res, next, id){
req.hash_id = id;
return next();
});
app.use(function(req, res, next){
console.log(req.hash_id);
return next();
});
I don't think you can use req.params inside a middleware function as it is bound to specific routes. You could use req.query though, but then you have to write your routes differently, e.g. /user?hash=12345abc. Not sure about passing the value from app.param to app.use.
If you have a specific structure for your routes, like /user/:hash you could simply write
// that part is fine
app.param('hash',function(req, res, next, id){
req.hash_id = id;
return next();
});
app.all('/user/:hash', function(req, res, next) { // app.all instead app.use
console.log(req.hash_id);
next(); // continue to sending an answer or some html
});
// GET /user/steve -> steve

Resources