Input data validation in web applications is a critical process that ensures the integrity, accuracy, and security of the data received from user inputs. It involves verifying that the data entered by users matches the expected format, type, and content before processing or storing it. Effective input validation can prevent a wide range of issues, including security vulnerabilities, data corruption, and application errors.
POST /api/users)(or POST /api/auth/register)
express-validatorExample database query function in user-model.js:
/**
* Creates a new user in the database
*
* @param {object} user data
* @returns {number} - id of the inserted user in db
*/
const addUser = async (user) => {
try {
const sql = `INSERT INTO Users (username, email, password, user_level_id)
VALUES (?, ?, ?, ?)`;
// user level id defaults to 2 (normal user)
const params = [user.username, user.email, user.password, 2];
const result = await promisePool.query(sql, params);
return result[0].insertId;
} catch (e) {
console.error('error', e.message);
return {error: e.message};
}
}
Add validation middleware to the route handler and validation rules to the request body fields
...
import {body} from 'express-validator';
...
// routes for /api/users/
userRouter.route('/')
.get(getUsers)
.post(
body('email').trim().isEmail(),
body('username').trim().isLength({min: 3, max: 20}).isAlphanumeric(),
body('password').trim().isLength({min: 8}),
postUser
);
...
Handle validation errors in controller
import {validationResult} from 'express-validator';
import {addUser} from '../models/user-model.js';
const postUser = async (req, res) => {
// validation errors can be retrieved from the request object (added by express-validator middleware)
const errors = validationResult(req);
// check if any validation errors
if (!errors.isEmpty()) {
return res.status(400).json({errors: errors.array()});
}
const newUserId = await addUser(req.body);
res.json({message: 'new user added', user_id: newUserId});
};
...
POST /api/entries)Add validation middleware to the route handler and validation rules to the request body fields
...
import {body} from 'express-validator';
...
entryRouter
.route('/')
.get(getEntries)
.post(
authenticateToken,
// TODO: add validation rules here
postEntry);
...
Handle validation errors in controller
import {validationResult} from 'express-validator';
...
const postEntry = async (req, res) => {
// validation errors can be retrieved from the request object (added by express-validator middleware)
const errors = validationResult(req);
// check if any validation errors
if (!errors.isEmpty()) {
return res.status(400).json({errors: errors.array()});
}
// TODO: call model function to add the valid entry item to database
...
};
...
Test the endpoint with Postman or VSC REST Client
(err, req, res, next)
Add error handler middleware functions to middlewares/middlewares.js (or create a new file middlewares/error-handler.js):
...
const notFoundHandler = (req, res, next) => {
const error = new Error(`Not Found - ${req.originalUrl}`);
error.status = 404;
next(error); // forward error to error handler
};
/**
* Custom default middleware for handling errors
*/
const errorHandler = (err, req, res, next) => {
res.status(err.status || 500); // default is 500 if err.status is not defined
res.json({
error: {
message: err.message,
status: err.status || 500
}
});
};
...
Import the error handler middleware in index.js and add it as the last middleware in the chain
...
// Import error handler middlewares on the top of the file
...
...
// Default for all routes not handled by routers above
app.use(notFoundHandler);
// Add error handler middleware as the last middleware in the chain
app.use(errorHandler);
...
next() to pass the error to the error handler middleware, some examples: // user-controller.js
import {validationResult} from 'express-validator';
import {addUser} from '../models/user-model.js';
const postUser = async (req, res, next) => {
// validation errors can be retrieved from the request object (added by express-validator middleware)
const errors = validationResult(req);
// check if any validation errors
if (!errors.isEmpty()) {
// pass the error to the error handler middleware
const error = new Error('Invalid or missing fields');
error.status = 400;
return next(error);
}
// TODO: add password hashing here and error handling for SQL errors
const newUserId = await addUser(req.body);
res.json({message: 'new user added', user_id: newUserId});
};
...
// entry-controller.js
const postEntry = async (req, res, next) => {
const errors = validationResult(req);
// check if any validation errors
if (!errors.isEmpty()) {
console.log('postEntry errors', errors.array());
const error = new Error('Invalid or missing fields');
error.status = 400;
return next(error);
}
// get fields from request body
const {entry_date, mood, weight, sleep_hours, notes} = req.body;
// req.user is added by authenticateToken middleware
const user_id = req.user.user_id;
// combine fields into a new entry object
const newEntry = {user_id, entry_date, mood, weight, sleep_hours, notes};
const result = await addEntry(newEntry);
if (result.error) {
return next(new Error(result.error));
}
res.status(201).json({message: 'New entry added.', ...result});
};
...
// middlewares/error-handler.js
/**
* Custom middleware for handling and formatting validation errors
* @param {object} req - request object
* @param {object} res - response object
* @param {function} next - next function
* @return {*} next function call
*/
const validationErrorHandler = (req, res, next) => {
const errors = validationResult(req, {strictParams: ['body']});
if (!errors.isEmpty()) {
// console.log('validation errors', errors.array({onlyFirstError: true}));
const error = new Error('Bad Request');
error.status = 400;
error.errors = errors.array({onlyFirstError: true}).map((error) => {
return {field: error.path, message: error.msg};
});
return next(error);
}
next();
};
...
---
// example usage in user registration route
import {validationErrorHandler} from '../middlewares/error-handler.js';
...
userRouter.route('/').post(
body('username', 'username must be 3-20 characters long and alphanumeric')
.trim()
.isLength({min: 3, max: 20})
.isAlphanumeric(),
body('password', 'minimum password length is 8 characters')
.trim()
.isLength({min: 8, max: 128}),
body('email', 'must be a valid email address')
.trim()
.isEmail()
.normalizeEmail(),
validationErrorHandler,
postUser,
);
// user-controller.js
import {validationErrorHandler} from '../middlewares/error-handler.js';
...
userRouter.route('/')
.get(getUsers)
.post(
validationErrorHandler,
postUser
);
...
POST /api/users with an empty request bodyPOST /api/users with invalid email address/username/passwordPOST /api/entries with an empty request bodyPOST /api/entries with some invalid field valuesvalidation
Returning: See related assignment in Oma.