I have an Express application and I'm trying to put all my middleware in its own file. Some of the middleware functions need the db object and some don't.
It's pretty straightforward for the functions that don't need the db object, but given my code structure below, how can I reference the db object in doesNotNeedDbParam since it already has params req, res, and next?
somefile.js:
const router = express.Router()
const doesNotNeedDbParam = require('./middleware')().doesNotNeedDbParam
function foo () {
// Currently I have to call require and pass in the db object here b/c
// it's not set when requiring the function doesNotNeedDbParam
router.use(require('./middleware')(db).needsDbParam // <-- Is there a better way to do this so that I can require the file above and pass the db object in when it's set?
}
// Setup db object here
foo()
middleware.js
function doesNotNeedDbParam (req, res, next) {
...
}
function needsDbParam (req, res, next) {
// Where do I reference the db variable?
}
module.exports = (db) => {
return {
doesNotNeedDbParam: doesNotNeedDbParam,
needsDbParam: needsDbParam
}
}
Functional Approach
I think a good structure for this is to try currying your middleware. This is a pattern practiced by middleware such as body-parser and internally by Express itself with serve-static. This way, you only have to require once, and pass db where you need to, and don't where you don't need it:
// Instead of declaring each function directly as a middleware function,
// we declare them as a function that returns a middleware function
function doesNotNeedDbParam () {
return function (req, res, next) {
…
}
}
function needsDbParam (db) {
return function (req, res, next) {
// You can use db here along with req, res, next
}
}
// No need to export a function now
module.exports = {
doesNotNeedDbParam,
needDbParam,
};
Then, just require:
const middleware = require('./middleware');
…
router.use(middleware.doesNotNeedDbParam()); // Since this doesn't need anything, no argument
router.use(middleware.needsDbParam(db)); // You can pass db here now
If you're comfortable with ES6+ syntax, you can condense to:
const doesNotNeedDbParam = () => (req, res, next) => {
…
}
const needsDbParam = (db) => (req, res, next) => {
// Use db here
}
// Export object here...
Then:
const { doesNotNeedDbParam, needsDbParam } = require('./middleware');
…
router.use(doesNotNeedDbParam());
router.use(needsDbParam(db));
Attach Approach
There's also another way you can do this, by attaching a property to the req object once. This removes the need to repass db every single time you want it. Many other packages use this strategy. It goes something like this:
function attachDb (db) { // Still use curry approach here since we want db
return function (req, res, next) {
// Attaches the specified db to req directly
req.db = db;
}
}
function needsDbParam (req, res, next) { // No need for currying
// Now use req.db here
}
// Export your other middleware…
Then, use it like so, make sure attachDb is first so that the property is assigned before you use it:
router.use(attachDb(db)); // Before all other middleware that depend on req.db
…
// No need to call because this is already the middleware function,
// able to use req.db, which was assigned via attachDb
router.use(needDbParam);
Why not just declare module.exports as a single function:
module.exports = (db) => {
let module = {};
module.doesNotNeedDbParam = (req, res) => {
// Do Stuff
};
module.needsDbParam = (req, res) => {
// db now in scope
};
return module;
};
This is what your somefile.js would become:
const router = express.Router();
const db = initializeDb();
const doesNotNeedDbParam = require('./middleware')().doesNotNeedDbParam;
router.use(require('./middleware')(db).needsDbParam);
You could also set it up once like this:
const middleware = require('./middleware')(db);
const doesNotNeedParam = middleware.doesNotNeedParam;
router.use(middleware.needsDbParam);
This isn't really any different than what you were doing before, but now you have access to db inside of needsDbParam. If your initializeDb function is async, then you will need to use Promise or some other async library to include after the db is set up.
Related
So I'm doing a simple NodeJS app with MongoDB/Express/Mongoose. In my mongoose schema I have a field (pictureURL) with a default value, the problem is that if pictureURL is an empty string the default value does not get applied. To solve this I though about using a custom middleware when doing the POST request either when creating or updating the model.
But the issue I'm having is that from within the middleware req.body is undefined. It is fine when in the router.post method but not in the middleware. Here is the code I have.
middleware (pictureU.js)
const app = require('../app');
const bookPictureUrl = (res, req, next) => {
console.log({ body: req.body });
if (!req.body.pictureUrl)
req.body.pictureUrl = 'images/default';
next();
};
module.exports = { bookPictureUrl };
book.routes.js
const app = require('../app');
const router = require('express').Router();
const Book = require('../models/Book.model');
const { bookPictureUrl } = require('../middleware/pictureUrl');
router.post('/update/:id', bookPictureUrl, async (req, res, next) => {
try {
req.body.authors = req.body.authors.split(',');
const data = await Book.findByIdAndUpdate(req.params.id, req.body);
res.redirect('/books');
} catch (err) {
next(err);
}
});
Any help trying to fix this so that I can use req.body within the middleware would be greatly appreciate.
Thanks
You mixed up your argument order. req should come before res.
const bookPictureUrl = (req, res, next) => {
console.log({ body: req.body });
if (!req.body.pictureUrl)
req.body.pictureUrl = 'images/default';
next();
};
My Express API exposes a POST endpoint to create a user, and i need to validate data before inserting it into database, i have two methods in mind:
Method 1: Include the model's validation in the controller and repeat it for every model:
// controllers/users.js
exports.createUser = async function (req, res) {
const { username, email, password } = req.body;
/* validation logic */
/* interact with db */
Method 2: Move the validation logic to dedicated middleware:
// middleware/validators.js
exports.validateArticle = function (req, res, next) {};
exports.validateComment = function (req, res, next) {};
exports.validateUser = function (req, res, next) {
const { username, email, password } = req.body;
/* validation logic */
if (!isValid) {
return res.statusCode(400);
}
next();
};
// routes/users.js
const { validateUser } = require('../middlewares/validators');
router.route('/').post(validateUser, createUser);
my concern with method 2 is that the logic for one endpoint method would be scattered among many files, but which one of these methods follow best practices ?
I can suggest you to use a ready-made middlewares express-validator, and setup like that:
// src/validation/validation.js
const { validationResult } = require('express-validator');
const validate = (schemas) => {
return async (req, res, next) => {
await Promise.all(schemas.map((schema) => schema.run(req)));
const result = validationResult(req);
if (result.isEmpty()) {
return next();
}
const errors = result.array();
return res.send({
message: 'Validation error',
errors: errors,
})
};
}
module.exports = {
validate
}
this is a function that you can call like middleware in your router, then a file with validation rules.
// src/validation/validationSchemas.js
const { body } = require('express-validator');
const addUserSchema = [
body('username').isLength({ min: 4, max: 16 }).withMessage('Username must be at least 4 and no more than 16 characters'),
body('email').isEmail().withMessage('Incorrect email')
];
module.exports = { addUserSchema }
and in your router:
const { validate } = require('../validation/validation');
const { registrationSchema, loginSchema } = require('../validation/validationSchemas');
router.post('/registration', validate(registrationSchema), userController.registration);
The issue is one of scale. If there are a lot of different routes in a controller, or if you just have multiple controllers, it can be difficult to keep things clean and easy to understand. The controller shouldn't have much more than what is necessary to show how incoming requests are being routed and returned. Everything else that isn't trivial should be passed to a middleware service. Therefore, the second option tends to work out better if you want room to grow.
Additionally, by putting all your validation logic together you can easily reuse code where applicable.
Option 1 can work out if this is very static and you have no expectations that new routes or controllers will be added.
I have an async function as a route handler, and i'd like to have errors handled as some kind of middleware. Here is my working attempt:
router.get(
"/",
asyncMiddleware(
routeProviderMiddleware(
async ({ y }) => ({
body: await db.query({x: y})
})
)
)
)
// This is the middleware that catches any errors from the business logic and calls next to render the error page
const asyncMiddleware = fn =>
(req, res, next) => {
Promise.resolve(fn(req, res, next))
.catch(next)
}
// This is a middleware that provides the route handler with the query and maybe some other services that I don't want the route handler to explicitly access to
const routeProviderMiddleware = routeHandlerFn => async (req, res) => {
const {status = 200, body = {}} = await routeHandlerFn(req.query)
res.status(status).json(body)
}
What I strive to is a way to make the route declaration cleaner - I don't want the 2 middleware wrappers there, ideally i'd like for the business logic function there only, and somehow declare that every route is wrapped in these.
Even combining the two middlewares together would be nice, but I didn't manage.
I use following approach:
Create asyncWrap as helper middleware:
const asyncWrap = fn =>
function asyncUtilWrap (req, res, next, ...args) {
const fnReturn = fn(req, res, next, ...args)
return Promise.resolve(fnReturn).catch(next)
}
module.exports = asyncWrap
All your routes/middlewares/controllers should use this asyncWrap to handle errors:
router.get('/', asyncWrap(async (req, res, next) => {
let result = await db.query({x: y})
res.send(result)
}));
At app.js, the last middleware will receive the errors of all asyncWrap:
// 500 Internal Errors
app.use((err, req, res, next) => {
res.status(err.status || 500)
res.send({
message: err.message,
errors: err.errors,
})
})
Express 5 automatically handles async errors correctly
https://expressjs.com/en/guide/error-handling.html currently says it clearly:
Starting with Express 5, route handlers and middleware that return a Promise will call next(value) automatically when they reject or throw an error. For example:
app.get('/user/:id', async function (req, res, next) {
var user = await getUserById(req.params.id)
res.send(user)
})
If getUserById throws an error or rejects, next will be called with either the thrown error or the rejected value. If no rejected value is provided, next will be called with a default Error object provided by the Express router.
I have shown that in an experiment at: Passing in Async functions to Node.js Express.js router
This means that you will be able to just make the callback async and use await from it directly without any extra wrappers:
router.get("/", async (req, res) =>
const obj = await db.query({x: req.params.id})
// Use obj normally.
)
and errors will be correctly handled automatically.
Express permits a list of middlewares for a route and this approach sometimes works for me better than higher-order functions (they sometimes look like an overengineering).
Example:
app.get('/',
validate,
process,
serveJson)
function validate(req, res, next) {
const query = req.query;
if (isEmpty(query)) {
return res.status(400).end();
}
res.locals.y = query;
next();
}
function process(req, res, next) {
Promise.resolve()
.then(async () => {
res.locals.data = await db.query({x: res.locals.y});
next();
})
.catch((err) =>
res.status(503).end()
);
}
function serveJson(req, res, next) {
res.status(200).json(res.locals.data);
}
What you can do is add an error handlers after your routes. https://expressjs.com/en/guide/error-handling.html
app.use(function (err, req, res, next) {
console.error(err.stack)
res.status(500).send('Something broke!')
})
What I ended up doing is unifying the wrappers like this:
const routeProvider = routeHandlerFn => async (req, res, next) => {
try {
const {status = 200, body = {}} = await routeHandlerFn(req.query)
res.status(status).json(body)
} catch(error) {
next(error)
}
}
This wrapper is all any route would need. It catches unexpected errors and provides the route handler with the needed params.
I am creating routes in express js from json file with following structure
{
"/home":{
"token":"ksdjfglkas"
},
"/logout":{
"token":"ksdjfglksaudhf"
}
}
I need to be able to access the token inside the routes function. The js that i am using for generating the route is
for(var endpoint in context){
var route = context[endpoint];
app.use(endpoint,
function(req,res,next){
req.token= route.token;
next();
},
require('./route-mixin'));
}
The problem that i am facing is that route-mixin method always gets the last token.context in this case is just the js file i added above. How can i pass different tokens for each route individually.
The solution to this problem is to put the content within the loop into a closure.
What gave me the idea what's the issue in the first place, was the PhpStorm IDE:
The error message mutable variable is accessible from closure appeared within the first middleware. This article Mutable variable is accessible from closure. How can I fix this? gave me then the hint to use a closure.
So all what was necessary to get it running was changing:
for(var endpoint in context){
var route = context[endpoint];
app.use(endpoint,
function (req, res, next) {
req.token = route.token;
next();
},
function (req, res) {
console.log(req.token);
res.send('test');
}
);
}
to:
for(var endpoint in context){
(function() {
var route = context[endpoint];
app.use(endpoint,
function (req, res, next) {
req.token = route.token;
next();
},
function (req, res) {
console.log(req.token);
res.send('test');
}
);
})();
}
The full example code I was successfully running:
var express = require('express');
var app = express();
var context = {
"/home":{
"token":"ksdjfglkas"
},
"/logout":{
"token":"ksdjfglksaudhf"
}
};
for(var endpoint in context){
(function() {
var route = context[endpoint];
app.use(endpoint,
function (req, res, next) {
req.token = route.token;
next();
},
function (req, res) {
console.log(req.token);
res.send('test');
}
);
})();
}
app.listen(3000);
I'm working on an Nodejs API Client that takes this simple form:
//client.js
function Client (appId, token) {
if (!(this instanceof Client)) {
return new Client(appId, token);
}
this._appId = appId;
this._token = token;
...
}
Client.prototype.save = function (data,callback) {
return this_request(...);
}
Client.prototype._request = function (method, url, data, callback) {
//do stuff
}
module.exports = Client
I would now like to make an auth function available as middleware in Expressjs routes but I'm not sure how to integrate the function into client.js.
var myModule = require('myModule');
var thingy = myModule("12345", 'abcde');
router.get('/protectedRoute', thingy.auth, function(req, res, next){
}
For example, should the function be defined as part of the prototype like this:
Client.prototype.auth = function(req,res,next) {
//do stuff
}
Any pointers and recommendations much appreciated.
So the way middleware is implemented, is that you have to pass a function for it to then get called with req res and next. As you've taken the OOP approach and made auth a prototype method on an object, when that function gets called by express, it will have a different this scope, and would thus likely throw an error. You need to use bind to create a function that binds a specific scope to that function, and then pass that to express.
router.get('/protectedRoute', thingy.auth.bind(thingy), function(req, res, next){
}
Alternatively:
var authMiddleware = function(req, res, next) {
thingy.auth(req, res, next);
}
router.get('/protectedRoute', authMiddleware, function(req, res, next){
}