It all started with a piece of code that I saw while reviewing an express based application.
... and it wasn't something that I was seeing for at the first time. I've seen applications like this since long back, almost all applications generated with express-generator.
var express = require('express');
var router = express.Router();
const authorizationMiddleware = require("../authorizationMiddleware")
const payloadValidationMiddleware = require("../payloadValidationMiddleware")
const createAction = require("createAction")
const serviceRequest = require("serviceRequest")
/* POST request to filter users listing. */
router.post('/', authorizationMiddleware, payloadValidationMiddleware, async function(req, res, next) {
//Some code
const action = createAction(req);
const response = await serviceRequest(action);
//Some code to modify the response
res.send(response);
});
module.exports = router;
My obvious questions were, why can't createAction
and serviceRequest
also be middlewares?
And how can this be made prettier?
For the first question obviously, the answer was, the developer didn't know what is the standard way to pass on data from one middleware to another middleware. That's a knowledge issue.
The second one obviously, kept me up in the night for sometime. What could be the abstractions that can be done, so that there is less code to be written for the developers.
In a standard express app (which exposes restful APIs, not the kind that spits out a UI) , its all about middlewares. The whole application can(and should) be composed with middlewares. and the routes are
So, after some fiddling around, this is what I came up with.
Create an abstraction for router
// router.js
const express = require("express");
const createRouteConfig = ({
path,// /filter or /filter/:paramOne/:paramTwo etc.
method, //"GET"
middlewares, // an array of route middlewares [middleware1, middleware2, ..., middlewareN],
controller, // the final request handler
}) => {
const router = express.Router()
if (middlewares.length) {
router.route(path)[method.toLowerCase()](...middlewares, controller);
} else {
router.route(path)[method.toLowerCase()](controller);
}
return router;
};
module.exports = createRouteConfig;
And then in My Route Files, what I can do is
//User Route
const createRouteConfig = require("../router");
const authMiddleware = require("../authMiddleware")
const validateMiddleware = require("../validateMiddleware")
const actionMiddleware = require("actionMiddleware")
const serviceMiddleware= require("serviceMiddleware")
const userController = (req, res, next) => {
res.send({});
};
const route = createRouteConfig({
path: "/",
middlewares: [authMiddleware, validateMiddleware, actionMiddleware, serviceMiddleware],
method: "post",
controller: userController,
});
//Finally set the route to the app
app.use("/users", route); // here app is the express app.
Hmmmm.. Code was already looking pretty. I didn't do much, all I did was move things around, and create an api for creating routes.
PS:
I almost published this without answering the first question. The express-approved way of passing data between middlewares is using res.locals
object. In one middleware, you set
const authMiddleware = (req, res, next) => {
// your logic
res.locals.authenticated = true;
next();
}
and then in the next middleware you can read them as
const validateMiddleware = (req, res, next) => {
const {authenticated} = res.locals;
if(authenticated){
// your business logic
next();
} else {
// throw error, or respond with 401
}
}
Top comments (0)