Libraries like these are great but wouldn’t it be even better if we could use them in a more seamless way, like in a Request pipeline? Let’s have a look firstly how we would use Joi in an Express app in Node.js:
const Joi = require('joi');
app.post('/blog', async (req, res, next) => {
const { body } = req; const
blogSchema = Joi.object().keys({
title: Joi.string().required
description: Joi.string().required(),
authorId: Joi.number().required()
});
const result = Joi.validate(body, blogShema);
const { value, error } = result;
const valid = error == null;
if (!valid) {
res.status(422).json({
message: 'Invalid request',
data: body
})
} else {
const createdPost = await api.createPost(data);
res.json({ message: 'Resource created', data: createdPost })
}
});
The above works. But we have to, for each route:
- create a schema
- call validate()
It’s, for lack of better word, lacking in elegance. We want something slick looking
Building a middleware
Let’s see if we can’t rebuild it a bit to a middleware. Middlewares in Express is simply something we can stick into the request pipeline whenever we need it. In our case we would want to try and verify our request and early on determine whether it is worth proceeding with it or abort it.
So let’s look at a middleware. It’s just a function right:
const handler = (req, res, next) = { // handle our request }
const middleware = (req, res, next) => { // to be defined }
app.post( '/blog', middleware, handler )
It would be neat if we could provide a schema to our middleware so all we had to do in the middleware function was something like this:
(req, res, next) => {
const result = Joi.validate(schema, data)
}
We could create a module with a factory function and module for all our schemas. Let’s have a look at our factory function module first:
const Joi = require('joi');
const middleware = (schema, property) => {
return (req, res, next) => {
const { error } = Joi.validate(req.body, schema);
const valid = error == null;
if (valid) {
next();
} else {
const { details } = error;
const message = details.map(i => i.message).join(',');
console.log("error", message);
res.status(422).json({ error: message }) }
}
}
module.exports = middleware;
Let’s thereafter create a module for all our schemas, like so:
// schemas.js
const Joi = require('joi')
const schemas = {
blogPOST: Joi.object().keys({
title: Joi.string().required
description: Joi.string().required()
})
// define all the other schemas below
};
module.exports = schemas;
Ok then, let’s head back to our application file:
// app.js
const express = require('express')
const cors = require('cors');
const app = express()
const port = 3000
const schemas = require('./schemas');
const middleware = require('./middleware');
var bodyParser = require("body-parser");
app.use(cors());
app.use(bodyParser.json());
app.get('/', (req, res) => res.send('Hello World!'))
app.post('/blog', middleware(schemas.blogPOST) , (req, res) => {
console.log('/update');
res.json(req.body);
});
app.listen(port, () => console.log(`Example app listening on port ${port}!`))