best practice to validate POST request body - javascript

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.

Related

req.body undefined in custom middleware express

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();
};

Setup an authentication middleware to reduce duplicate code in Express.js

As the title suggests, I want to reduce duplicate authorization code for each new route I call.
My problem is exactly the same as the user in this post, because apparently we downloaded the same project from GitHub repository.
I tried both of the solutions suggested in the answers, however it restricts me from accessing those routes even if I'm logged in.
Here's the code:
router.js
// GET route for reading data
router.get("/", function (req, res, next) {
return res.sendFile(path.join(__dirname + "/login"));
});
//Export authorization module
var auth = require("../auth");
//Verify if user is authorized to access this route
router.get("/complete-profile", auth.isAuthorized, function (req, res, next) {
return res.sendFile(path.join(__dirname, "../public", "image.html"));
});
//READ THE IMAGE UPLOAD FOLDER
router.use(express.static("public"));
// GET route after login, verify if user logged in
router.get("/complete-profile", function (req, res, next) {
User.findById(req.session.userId).exec(function (error, user) {
if (error) {
return next(error);
} else {
if (user === null) {
var err = new Error("Not authorized! Go back!");
err.status = 400;
return next(err);
} else {
//SEND NEW USERS TO IMAGE UPLOAD PAGE
return res.sendFile(path.join(__dirname, "../public", "image.html"));
}
}
});
});
As suggested, I tried declaring all of this as a middleware, so here is the middleware:
auth.js
module.exports.isAuthorized = function(req, res, next) {
User.findById(req.session.userId).exec(function (error, user) {
if (error) {
return next(error);
} else {
if (user === null) {
var err = new Error('Not authorized! Go back!');
err.status = 400;
return next(err);
} else {
return next();
}
}
});
}
Any help is gladly appreciated!
Source: How to setup an authentication middleware in Express.js
In the answer you referenced, it appears that user installed and is using Sequelize to store an individual's user data. If you would like to utilize that approach, I would look into Sequelize. As you mentioned on the other thread, User is not defined. For the other question, the asker most likely set up a model called User.
In Sequelize, each model (like User) defines a table that has its own rows and columns. Each column represents a field that applies to an individual row of data. For example, for a User model, one user may have a username, an email, and a password. You would specify what data types these columns should be and any other necessary information for each column of the Sequelize model definition. Each row represents one data-entry, or in this case, one user. I had previously built a sample web app that maps students to specific classes; below I have copied the Sequelize model definition I wrote for that project. It's quite simple and I would recommend watching some YouTube tutorials or checking out the Sequelize documentation at sequelize.org if this library is foreign to you.
Student.js
'use strict';
const Sequelize = require('sequelize');
const db = require('./_db');
const Student = db.define('student', {
name: {
type: Sequelize.STRING,
allowNull: false,
validate: {
notEmpty: true
}
},
phase: {
type: Sequelize.STRING,
allowNull: true,
validate: {
isIn: [['junior', 'senior', null]]
}
}
});
Student.findByPhase = async function(phase) {
const students = await Student.findAll({
where: {
phase: phase
}
})
return students
}
module.exports = Student;
It may also help to check out PostgreSQL or SQL in general as well to understand the basic framework that Sequelize lies on top of.

My console gives me an error when I put the routes to a controller

I'm setting up a route to register an account, but my VS code gives me an error when I direct the routes to controller.js.
This is for a new website, running javascript, nodejs and react.
My routes.js:
const routes = require('express').Router();
const RegisterController = require('./controllers/RegisterController');
routes.get('/', (req, res) => {
return res.send('Hello, World!')
});
routes.post('/register', RegisterController.store);
module.exports = routes;
My RegisterController.js:
const User = require('../models/UserModel');
class RegisterController {
async store(req, res) {
const email = req.body.email.toLowerCase();
const username = req.body.username.toLowerCase();
const EmailExists = await User.findOne({ email: email });
const UserExists = await User.findOne({ user: username });
if (!EmailExists) {
return res.json({ message: 'This email is already registered! Please, try another.' });
};
if (!UserExists) {
return res.json({ message: 'This user is already registered! Please, try another.' });
}
const { password } = req.body.password;
const user = await User.create({
username,
email,
password
});
return res.json(user)
}
}
I expect to register my MongoDB account.
The error VS Code gives me is:
Error: Route.post() requires a callback function but got a [object Undefined]
RegisterController.store is undefined because store is a instance method instead of a static method.
class RegisterController {
static async store(req, res) {
// ...
}
}
It's a naming problem:
const RegisterController = require('./controllers/RegisterController');
if you have a class with a static function e.g.:
class RegisterController {
static store(req, res) {}
}
Then you would call it like:
RegisterController.store(req, res);
If you make the variable name the same as the class name, then how would you make a distinction between a static call and a normal call?
const RegisterController = require('./controllers/RegisterController');
RegisterController.store(req, res); // this is meant to be a static call
RegisterController.store(req, res); // this is meant to be a normal call
You create your variable name with uppercase, in my opinion it should be lowercase because else how would you make a difference between a class name and a variable name just by looking at it?
Try the following:
const registerController = require('./controllers/RegisterController');
routes.post('/register', registerController.store);
I hope this provides a solution to your problem.

How can I return Multer Error to Client while using Express-Validator?

Updated Post: Scroll to bottom of post for updated info
Original Post
I need some help on this one. I'm creating a route that takes FormData, validates the file data via Multer (images in this case), then validates the string data using Express-Validator. I've created a working route that completes both validations, but I can't figure out how to take any errors from Multer and return them to the client.
I have set Multer before Express-Validator, so that the req.body can be read by Express-Validator. With that, I can't figure out how to (or if I'm able at all) to pass in the Multer errors for sending back in the response.
My example below should include everything needed for examination, but if you need additional information, please let me know.
const multer = require('multer')
const {
check,
validationResult
} = require('express-validator/check');
const {
sanitizeBody
} = require('express-validator/filter');
const imageUpload = multer({
dest: 'uploads/',
limits: {
fileSize: 1000000
},
fileFilter: function (req, file, cb) {
let filetypes = /jpeg|jpg/;
let mimetype = filetypes.test(file.mimetype);
let extname = filetypes.test(path.extname(file.originalname).toLowerCase());
if (mimetype && extname) {
return cb(null, true);
}
cb(new Error('Invalid IMAGE Type'))
}
}).fields([{
name: 'cover_image',
maxCount: 1
},
{
name: 'more_images',
maxCount: 2
}
])
const validationChecks = [
check('street', 'Invalid Street Name').matches(/^[a-z0-9 ]+$/i).isLength({
min: 1,
max: 25
}).trim().escape(),
check('city', 'Invalid City Name').matches(/^[a-z ]+$/i).isLength({
min: 1,
max: 15
}).trim().escape()
]
router.post('/addnewproperty', imageUpload, validationChecks,(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
console.log('text validation FAILED');
return res.status(400).json({
errors: errors.array()
});
}
console.log('validation PASSED');
})
Update 2/6/19
Okay, I think I have found a solution, although not what I expected.
By using the next() function within express, I'm able to utilize Multer in the first route handler where I can receive and send back Multer errors in the response. If no errors arise in this first route handler, I can call next(), to then move along to the next route handler for utilizing express-validator where I can check for and send any errors that arise from string validation.
The code below is a working example of what I'm describing. Not sure if this is acceptable code, but it is working upon some light testing. Any opinions or recommendations on this are welcome in the comments below.
// Here's the meat of what I changed.
// The config and variables set in the previous code are the same.
router.post('/addnewproperty',(req, res, next) => {
imageUpload(req,res,(err)=>{
if(err){
console.log(err.message);
return res.status(400).json(err.message)
}
next()
})
})
router.post('/addnewproperty',validationChecks,(req,res)=>{
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({
errors: errors.array()
});
}
return res.sendStatus(200)
})
I'll leave this question open in case someone has a better solution of obtaining what I originally set out to do besides the code above.
I have been using a function that creates a Multer middleware that can be placed anywhere in the middleware chain. After it, you can use req.body without the other binary fields.
import { Router } from 'express';
import multer from 'multer';
function makeMulterUploadMiddleware(multerUploadFunction) {
return (req, res, next) =>
multerUploadFunction(req, res, err => {
// handle Multer error
if (err && err.name && err.name === 'MulterError') {
return res.status(500).send({
error: err.name,
message: `File upload error: ${err.message}`,
});
}
// handle other errors
if (err) {
return res.status(500).send({
error: 'FILE UPLOAD ERROR',
message: `Something wrong ocurred when trying to upload the file`,
});
}
next();
});
}
const upload = multer({ dest: 'uploads/' });
const multerUploadMiddleware = makeMulterUploadMiddleware(upload.single('image'));
const someRouter = Router();
someRouter.post('', multerUploadMiddleware, (req, res) => {
// now body contains all the fields except the one with the file
res.send(req.body);
});
export { someRouter };
I'm handling the req.body with the #hapi/joi npm package, but this should work with other validators.
note: The reason I'm not using err instanceof multer.MulterError to check if its a Multer error as described in the Multer docs (https://github.com/expressjs/multer) is because of some typescript type-checking errors.
You can get the error by calling the imageUpload middleware directly instead of using it in a middleware chain like in your code.
Non-tested snippet, but hopefully will at least nudge you in the right direction:
router.post('/addnewproperty', validationChecks, (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
console.log('text validation FAILED');
return res.status(400).json({
errors: errors.array()
});
}
imageUpload(req, res, (multerErr) => {
if(multerErr){
console.log('Multer validation FAILED');
return res.status(400).json({
errors: [multerErr.message]
});
}else{
console.log('validation PASSED');
}
});
})
For more on the subject, here are the official Multer docs on Error handling.
Hey i will leave you with some snippets which worked for me ,
Multer without using file
const express = require('express');
const multer = require('multer');
const router = express.Router();
const { check, validationResult } = require('express-validator');
var upload = multer();
var tagCreateValidator = [
check('name', 'Name is required')
.not()
.isEmpty()
];
// #route POST api/tags
// #desc Create Tag
// #access Public
router.post('/', upload.any(), tagCreateValidator, (req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
res.send(req.body);
});
this one is with file although i am not using express validator
right now, i will update this answer once this is done
const express = require('express');
const Multer = require('multer');
const gcsMiddlewares = require('../../gcs_middleware');
const router = express.Router();
const multer = Multer({
storage: Multer.storage,
limits: {
fileSize: 10 * 1024 * 1024 //Maximum file size is 10MB
}
});
// #route POST api/users
// #desc Register user
// #access Public
router.post(
'/',
multer.single('avatar'),
gcsMiddlewares.sendUploadToGCS,
(req, res, next) => {
var imageUrl;
if (req.file && req.file.gcsUrl) {
imageUrl = req.file.gcsUrl;
}
var responseBody = {
file: imageUrl,
body: req.body
};
res.send(req);
}
);
module.exports = router;
I ran into this problem today except I am not using a router. I wanted to be able to return a JSON response of the error when the fileType validation failed. I thought I would share my solution as it may help somebody else. My solution was to add a fourth parameter to the app.post() function call like so:
app.post("/post", fileUpload.single('file'), function(req, res) {
//do some stuff after a successful upload.
},
function(err, req, res, next) {
//File upload encountered an error as returned by multer
res.status(400).json({error: err.message});
})

How to pass parameter only to middleware that need it?

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.

Categories