Express route param validation (Node.js)

Hi, my name is Tina! I am a Full Stack Software Developer and Tech Lead from Canada. 🙋🏻♀️
Search for a command to run...

Hi, my name is Tina! I am a Full Stack Software Developer and Tech Lead from Canada. 🙋🏻♀️
No comments yet. Be the first to comment.
Have you ever installed a package by using go install <some-package> and wondered how to uninstall it? Go is really great in that respect. Go packages are a single binary and they’re installed based on how your Go tooling is set up. Provided we have ...

Overview This is the 3rd article in a series of posts about working with Open API to create generated API clients and documentation websites. In the 1st post we went over what we can do with Open API spec files, e.g. generate API clients and documen...

Overview This article is the 2nd in a series of blog posts that discuss using Open API to help with API documentation and API client code generation. In the previous article, we went over how we can generate documentation and API clients using existi...

Overview This article starts a series of Open API-related blog posts. OpenAPI, previously known as Swagger (a name still in use today for many of its tools), is a suite of tools that generate documentation and networking clients for various programmi...

You've probably already seen the scary alert (that's possibly why you're here), but in case you haven't, it looks something like this: NewAppIJustDownloaded pasted from your clipboard Sometimes it h

If you do Node.js development you're probably familiar with Express and maybe even Hapi. If not, they both solve the problem of server-side routing for APIs and server-rendered apps. Update (May 25, 2019): The Hapi team have deprecated their joi package in favour of @hapi/joi so the content of the blog post has been updated to reflect that change.
I'm not going to get into the pros and cons of each framework, I will simply list what I love about each of them. I love how Express is so simple and concise. It doesn't require much setup, and its concept of middleware is straightforward and easy. Hapi's middleware ecosystem is similar except they're called plugins. One great plugin is the Joi validation library. It integrates nicely with Hapi and can be used to validate route params.
If you've done any sort of back-end or full stack development, you'd know that it's important to validate the incoming request for the right parameters. For example, perhaps you'd like to validate an ID, you'd want to make sure it's in the format your app is looking for, e.g. auto-incrementing integer, or following a slug format, e.g. record-123abc. Or maybe you want to validate that a phone number is in the right format.
I wanted to take these two great things and put them together, so two years ago I wrote the express-joi-validate library. This library allows you to use the simplicity of Express while giving you powerful route parameter validation using Joi. My library implements Express's middleware API to process the request before it is passed off to your route handler. Let's say we have the following sample app in Express:
const express = require("express");
const app = express();
const port = process.env.PORT || 3000;
const contactsHandler = require('./handlers/contacts'); // resolves the request
app.get('/contacts/:id', contactsHandler);
app.listen(port, () => {
console.log(`Example app listening on port ${port}!`);
});
Without route parameter validation, a route in Express could look like this:
app.get('/contacts/:id', contactsHandler);
If we wanted to validate the ID param, we could do so in the handler, but arguably there's a better way! These kinds of concerns are best delegated to middleware. With route validation using express-joi-validate, it could look like this:
const validate = require('express-joi-validate');
const Joi = require('@hapi/joi');
const contactsHandler = require('./handlers/contacts');
const contactSchema = {
params: {
id: Joi.number().required()
}
};
app.get('/contacts/:id', validate(contactSchema), contactsHandler);
The above example is simple, requiring that ID be a number, but you can get really creative with validation. If the above validation fails, the server will return 400 Bad Request automatically. Joi comes with a bunch of validators, including lots of different string validation, e.g. email, phone number, dates, and if you like to get creative, even regular expression. Look at the Joi API reference to see what it can do.
You may ask why this is necessary. In my opinion, separating your API route validation from the handler makes sense in order to separate concerns.
This may be best illustrated with sample code.
In this example, the handler does the validation.
// route definition
const createContactHandler = require("./handlers/contact/create");
app.post("/contacts", createContactHandler);
// handler without express-joi-validate
const Contact = require("../models/contact");
const createContactHandler = (req, res) => {
if (
// Validate phone (optional, must match pattern)
(req.body.phone && !req.body.phone.match(/^[0-9]*$/)) ||
// Validate birthday (required, must match ISO date format pattern)
(!req.body.birthdate ||
!req.body.birthdate.match(
/(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))|(\d{4}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d([+-][0-2]\d:[0-5]\d|Z))/
)) ||
// Validate first name (required, letters only)
(!req.body.first_name || !req.body.first_name.match(/^[a-zA-Z]+$/))
) {
return res.send(400);
}
return Contact.create(req.body).then(contact => {
return res.json({ contact });
});
};
module.exports = createContactHandler;
In this example, the middleware handles the validation, which separates the concerns and keeps the handler code clean and tidy.
// route definition
const Joi = require("@hapi/joi");
const validate = require("express-joi-validate");
const createContactHandler = require("./handlers/contact/create");
// This could be moved to a separate file to keep this even cleaner
const contactSchema = {
body: {
first_name: Joi.string().required(),
birth_date: Joi.isoDate().required(),
phone: Joi.number()
}
};
app.post("/contacts", validate(contactSchema), createContactHandler);
// handler where express-joi-validate middleware was implemented
const Contact = require("../models/contact");
const createContactHandler = (req, res) =>
Contact
.create(req.body)
.then(contact => res.json({ contact }));
module.exports = createContactHandler;
Look how clean that handler code is! The handler ends up just being the call to create the model and return it. Joi has already solved the problem of validating dates, for example. Let's use it instead of writing our own validation code. It's also much nicer to read than the complex conditional statement.
You can download this library from NPM or from Github. See the documentation in the README file on the npm.js website.