Exclude route from express middleware - javascript

I have a node app sitting like a firewall/dispatcher in front of other micro services and it uses a middleware chain like below:
...
app.use app_lookup
app.use timestamp_validator
app.use request_body
app.use checksum_validator
app.use rateLimiter
app.use whitelist
app.use proxy
...
However for a particular GET route I want to skip all of them except rateLimiter and proxy. Is their a way to set a filter like a Rails before_filter using :except/:only?

Even though there is no build-in middleware filter system in expressjs, you can achieve this in at least two ways.
First method is to mount all middlewares that you want to skip to a regular expression path than includes a negative lookup:
// Skip all middleware except rateLimiter and proxy when route is /example_route
app.use(/\/((?!example_route).)*/, app_lookup);
app.use(/\/((?!example_route).)*/, timestamp_validator);
app.use(/\/((?!example_route).)*/, request_body);
app.use(/\/((?!example_route).)*/, checksum_validator);
app.use(rateLimiter);
app.use(/\/((?!example_route).)*/, whitelist);
app.use(proxy);
Second method, probably more readable and cleaner one, is to wrap your middleware with a small helper function:
var unless = function(path, middleware) {
return function(req, res, next) {
if (path === req.path) {
return next();
} else {
return middleware(req, res, next);
}
};
};
app.use(unless('/example_route', app_lookup));
app.use(unless('/example_route', timestamp_validator));
app.use(unless('/example_route', request_body));
app.use(unless('/example_route', checksum_validator));
app.use(rateLimiter);
app.use(unless('/example_route', whitelist));
app.use(proxy);
If you need more powerfull route matching than simple path === req.path you can use path-to-regexp module that is used internally by Express.
UPDATE :- In express 4.17 req.path returns only '/', so use req.baseUrl :
var unless = function(path, middleware) {
return function(req, res, next) {
if (path === req.baseUrl) {
return next();
} else {
return middleware(req, res, next);
}
};
};

Built upon the answer from #lukaszfiszer as I wanted more than one route excluded.
You can add as many as you want here.
var unless = function(middleware, ...paths) {
return function(req, res, next) {
const pathCheck = paths.some(path => path === req.path);
pathCheck ? next() : middleware(req, res, next);
};
};
app.use(unless(redirectPage, "/user/login", "/user/register"));
Can't add as comment sorry.

You can also skip route like this by putting a condition on req.originalUrl:
app.use(function (req, res, next) {
if (req.originalUrl === '/api/login') {
return next();
} else {
//DO SOMETHING
}

I use this regular expression with success : /^\/(?!path1|pathn).*$/.

There's a lot of good answers here. I needed a slightly different answer though.
I wanted to be able to exclude middleware from all HTTP PUT requests. So I created a more general version of the unless function that allows a predicate to be passed in:
function unless(pred, middleware) {
return (req, res, next) => {
if (pred(req)) {
next(); // Skip this middleware.
}
else {
middleware(req, res, next); // Allow this middleware.
}
}
}
Example usage:
app.use(unless(req => req.method === "PUT", bodyParser.json()));

You can define some routes like below.
app.use(/\/((?!route1|route2).)*/, (req, res, next) => {
//A personal middleware
//code
next();//Will call the app.get(), app.post() or other
});

Here's an example of using path-to-regexp as #lukaszfiszer's answer suggests:
import { RequestHandler } from 'express';
import pathToRegexp from 'path-to-regexp';
const unless = (
paths: pathToRegexp.Path,
middleware: RequestHandler
): RequestHandler => {
const regex = pathToRegexp(paths);
return (req, res, next) =>
regex.exec(req.url) ? next() : middleware(req, res, next);
};
export default unless;

The way I achieved this is by setting up a middleware for a specific path like so
app.use("/routeNeedingAllMiddleware", middleware1);
app.use("/routeNeedingAllMiddleware", middleware2);
app.use("/routeNeedingAllMiddleware", middleware3);
app.use("/routeNeedingAllMiddleware", middleware4);
and then setting up my routes like so
app.post("/routeNeedingAllMiddleware/route1", route1Handler);
app.post("/routeNeedingAllMiddleware/route2", route2Handler);
For the other special route that doesn't need all the middleware, we setup another route like so
app.use("/routeNeedingSomeMiddleware", middleware2);
app.use("/routeNeedingSomeMiddleware", middleware4);
and then setting up the corresponding route like so
app.post("/routeNeedingSomeMiddleware/specialRoute", specialRouteHandler);
The Express documentation for this is available here

In my case I used part of answers posted yet to override original app.use
const unless = ( route, middleware ) => {
return ( req, res, next ) => {
if ( req.originalUrl.startsWith( route + '/' ) ) {
return next();
} else {
return middleware( req, res, next );
}
};
};
const filteredRoute = '/myapi'; // Route to filter and subroute
const origUse = app.use;
app.use = function ( ...callbacks ) {
if ( !callbacks.length ) throw new Error( '.use() method requires at least one function' );
if ( typeof callbacks[0] ==='string' ) {
if ( !( callbacks.length -1 ) ) throw new Error( '.use() method requires at least one function' );
const route = callbacks.shift();
for ( let i = 0; i < callbacks.length; i++ ) {
origUse.call( this, route, unless( filteredRoute, callbacks[i] ) );
}
} else {
for ( let i = 0; i < callbacks.length; i++ ) {
origUse.call( this, unless( filteredRoute, callbacks[i] ) );
}
}
};

Improved upon #Geelie's answer with added types:
import {Request, Response, NextFunction, Handler} from "express";
const unless = (middleware: Handler, ...paths: RegExp[]): Handler => {
return function (req: Request, res: Response, next: NextFunction) {
const pathCheck = paths.some(path => path.test(req.path));
pathCheck ? next() : middleware(req, res, next);
};
};
app.use(unless(redirectPage, new RegExp("/user/login"), new RegExp("/user/register")));

Related

Making a get request from within a get request

I'm pretty new to node.js and express and I was wondering if there's a way to define a route that calls upon another route simply to collect data and not to completely reroute.
I've got a route set up as follows:
app.get("/databases/list", function(req, res) {
db.listDatabases().then(names => {
res.send(names);
});
});
Subsequently I'd like to have a different route, say:
app.get('/whatever', function(req, res) {
// here I'd like to make a call to retrieve the information from the first route
// then I'd like to do something with that information, I want to stay in the same route.
}
Is this possible?
Expanding #marcobiedermann answer, In your case simply make a controller and and use the FUNCTION in both the routes. You don't need to fetch anything.
/// --- Controller ----
class SimpleController {
constructor(db){
this.db = db;
}
listDatabase(/*maybe optional callback*/){
return this.db.listDatabases();//or something....
}
whatever(/*maybe optional callback*/){
return this.listDatabase()
.then(process)
}
}
/// --- Routes ----
const sController = new SimpleController(db);
app.get("/databases/list", function(req, res) {
sController.ListDatabase().then(names => {
res.send(names);
});
});
app.get('/whatever', function(req, res) {
sController.whatever()
.then(....)
}
Yes this is possible.
You have to fetch the data from your first endpoint.
fetch('/databases/list')
.then( … )
This requires the /databases/list route to be defined before your /whatever route.
However, I would strongly advice you to NOT do this.
You should abstract your logic into a controller and call this controller in both of your routes:
const fetchController = {
fetchData: () => {
return fetch('path/to/data/to/fetch')
.then( … )
// or database call or wherever you might get the data from
}
}
app.get('/databases/list', (req, res) => fetchController.fetchData());
app.get('/whatever', (req, res) => fetchController.fetchData());
app.get("/databases/list", async function(req, res) {
return await db.listDatabases();
});
app.get('/whatever', async function(req, res) {
const result = await fetch('path/databases/list');
console.log(result)
});
It might help you, But it's not recommended way. You can create method (common somewhere in the controller) and use that where ever you need.

How to modularize an express app - with a function, a class and req.pipe?

Here below there are two servers and two gqlServers. All combinations of them work.
The challenge is to extend express with some additional predefined code patterns shared across several apps, exposed through additional methods.
Which combination of a server and gqlServer is considered best practice and best for performance?
server:
server_A is a function that returns a class
server_B is a function that returns a function
gqlServer:
gqlServer_01 uses req.pipe
gqlServer_02 has the original express() passed into it
function gqlServer_01(options) {
let gqlApp = express();
gqlApp.use(options.route, function(req, res, next) {
res.send('gqlServer 01');
// next();
});
gqlApp.listen(8001, err => {
if (err) throw err;
console.log(`>> GQL Server running on 8001`);
});
}
function gqlServer_02(app, options) {
app.use(options.route, function(req, res, next) {
res.send('gqlServer 02');
// next();
});
}
// THIS SERVER ?
function server_A(config = {}) {
config = deepmerge(def_opt, config);
let app = express();
app.get('/', function(req, res, next) {
res.send('root');
// next();
});
class Server {
constructor(opt) {
this.opt = opt;
}
gql(props = {}) {
// THIS GQL SERVER ?
gqlServer_01({ route: '/gql-01' });
app.use('/gql-01', function(req, res) {
req.pipe(request(`http://localhost:8001/gql-01`)).pipe(res);
});
// OR THIS GQL SERVER ?
gqlServer_02(app, { route: '/gql-02' });
}
}
app.listen(8000, err => {
if (err) throw err;
console.log(`>> Server running on 8000`);
});
return new Server(app, config);
}
// OR THIS SERVER ?
function server_B(config = {}) {
config = deepmerge(def_opt, config);
let app = express();
app.get('/', function(req, res, next) {
res.send('root');
// next();
});
app.gql = function(props = {}) {
// THIS GQL SERVER ?
gqlServer_01({ route: '/gql-01' });
app.use('/gql-01', function(req, res) {
req.pipe(request(`http://localhost:8001/gql-01`)).pipe(res);
});
// OR THIS GQL SERVER ?
gqlServer_02(app, { route: '/gql-02' });
};
app.listen(8000, err => {
if (err) throw err;
console.log(`>> Server running on 8000`);
});
return app;
}
The goal is to have the best solution in order to create an npm package out of this and reuse the methods over several projects easily. The project was highly simplified for the sake of clarity.
I don't think you will have performance issues in any of these examples, so the question remains which of them is more modular.
If you are willing to make an npm package out of these, you shouldn't be calling express() inside your server code. Instead you should be passing the app as a parameter. This will allow you to reuse existing express apps initialized elsewhere. For this reason I would go for gqlServer_02
You also want to create a new server each time you call the module function, so I'd go with server_A for this reason. However it needs to receive the express app as parameter, in order to reuse existing express objects. I would also put the app.listen call inside a function in the Server class.

How to trigger next() in express Router.route and controller pattern?

I can't seem to figure out how to implement 'next()' after the request lands to one of my api routes. I want to do logging after everything is done, and not before my api routes. I have implemented the controller pattern on my routing. I can only do logging for now on errors, but I want to do logging on all requests:
//------ api/index.js
import routes from './routes/index.route';
app.use('/api', routes);
// ??? HOW TO HANDLE next() called in "/api routes"
// only handles incoming requests not found in "/api routes"
app.use((req, res, next) => {
// logic here...
next(error);
});
// global error handler
app.use((error, req, res, next) => {
// logging logic here...
});
//------ routes/index.route.js
import express from 'express';
import userRoutes from './user.route';
import bookRoutes from './book.route';
const router = express.Router();
router.use('/user', userRoutes);
router.use('/book', bookRoutes);
//------ routes/book.route.js
import express from 'express';
import bookCtrl from './../controllers/book.controller';
const router = express.Router();
router.route('/:bookId').get(bookCtrl.getBook);
//------ controllers/book.controller.js
export const getBook = async (req, res, next) => {
const book = // get book logic here;
return res.status(httpStatus.OK).json(book);
// HOW DO I TRIGGER next()?
// I tried this approach as well:
// res.status(httpStatus.OK).json(book);
// next();
//however, it would say, "Error: Can't set headers after they are sent."
}
Thank you so much.
I figured out a way to trigger next which is not sending a response immediately instead assigning it to res.locals and calling next. Here are the changes based on my above code:
//------ api/index.js
// handle next() event coming from "/api routes"
// and also handles incoming requests not found in "/api routes"
app.use((req, res, next) => {
if (res.locals.status) {
const { status, responseData } = res.locals;
res.status(status).json(responseData);
// logging logic here...
} else {
// error logic here...
// forward this error into the global error handler
next(error);
}
});
// controllers/book.controller.js
export const getBook = async (req, res, next) => {
// get book logic here..
const book = // get book logic here;
res.locals = {
status: httpStatus.OK,
responseData: {
book,
},
};
next();
}
Thanks to #destoryer for the res.json tip! :)
Just replace
return res.status(httpStatus.OK).json(book);
// HOW DO I TRIGGER next()?
with
res.status(httpStatus.OK).json(book);
next();
And it will work.

Dependency injection to module similar to using a class

I have this module in TypeScript (or ES2015, the code would be more or less identical) that I want to inject the route to:
var routeObject = express.Router();
var route: string = '/admin';
routeObject.get(route,
(req, res, next) => {
// Do something
return next();
},
jade.View('admin')
);
export default routeObject;
In my application.ts file I import the module and use it like this:
server.Express.use(observationsRoute);
I want to be able to inject the path/route just as if I would have been using a class so that I can write something like this:
server.Express.use(observationsRoute('/admin'));
How could you achieve this?
Export a functions that takes the route as an argument and return the routeObject:
export default (route: string) => {
var routeObject = express.Router();
routeObject.get(route + '/',
(req, res, next) => {
return next();
},
jade.View('admin')
);
return routeObject;
}

Manually chaining Express middleware

I currently use 2 middlewares:
Express-jwt which extracts/validates a JsonWebToken from a request and my own middleware that checks that the JWT contains specific information (permissions).
I want to conditionally use those middlewares together (based on whether there's a specific swagger attribute on a route).
I want to do something like this:
let expressjwt = function(req, res, next) { ... };
let jwtValidator = function(req, res, next) { ... };
app.use((res, req, next) => {
if(req.swagger.someAttribute) {
expressjwt(req, res, jwtValidator(req, res, next));
// The issue here is that jwtValidator will get called even if
// expressjwt produces an error
} else {
next();
}
});
It sounds like the question is - "how do you conditionally call service B only if service A succeeds."
This is one of main goals of promises - it allows you to chain together async calls and have them conditionally "resolve." I can post a code sample if needed.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
I ended up wrapping my first middleware in a Promise using Promise.fromCallback, from memory, something like this:
if (req.swagger.someAttribute) {
Promise.fromCallback(cb => expressjwt(req, res, cb))
.then(() => {
return jwtValidator(req, res, next);
})
.catch(next); // Or deal with the rejection
} else {
next();
}
Promise.fromCallback is useful because next() is only called with arguments if the middleware failed and thus will become promise.reject

Categories