Schema validation in Node.js using Joi
Securing the application by validating data at the query level with the Joi validation library is very simple. Here is a detailed guide on how to validate schema in Node.js using Joi .
Accepting untested and authenticated data into a web application can create security holes, and unpredictable crashes can arise from invalid data.
Node.js ORMs, such as Sequelize and TypeORM, allow you to set validation rules instantly at the application level. During API development, data travels from HTTP queries to specific endpoints. This happens at the query level, so the default authentication provided by ORMs is not applied to them.
Joi is a schema descriptor and data validation for JavaScript. Here, you will learn how to use the Joi validation library to validate data at the query level.
Set up test project
To demonstrate how Joi validates data, you will build a simple demo application that simulates an actual app.
First, create a project directory and access it by running the following command:
mkdir demoapp && cd demoapp
Next, initialize npm in the project directory by running:
npm init -y
Next, you need to install some dependencies. In this tutorial you will need:
- Express : Express is a Node.js framework that provides a powerful feature set for web and mobile applications. Express makes it easier to build backend applications with Node.js.
- Joi : Joi is a data validation library for Node.js.
Install the dependencies with the node package manager by running the following command:
npm install express joi
Next, create an index.js file in the root directory and add the following code block to it:
const express = require("express"); const router = require("./routes"); const port = 3000; const app = express(); app.use(express.json()); app.use(express.urlencoded({ extended: true })); app.use(router); app.listen(port, () => { console.log("app listening on port 3000!"); });
The above code block sets up a simple Express server. It configures middleware to analyze incoming query data, process incoming queries, and start the server to listen for incoming queries on port 3000.
Routing and handling requests
The simplest method is that you would create a middleware that processes the query, returning a status code, along with the query body, as a response to any request that tries to send data to your application.
Create the handler.js file in the project root directory and add the code block below:
const demoHandler = (req, res, next) => { res.send({ code: 201, data: req.body, }); next(); }; module.exports = demoHandler;
Next, create the file router.js in your project's root directory and add the following code block to your file:
const express = require("express"); const demoHandler = require("./handler"); const router = express.Router(); router.post("/signup", demoHandler); module.exports = router;
Create Joi Schema
A Joi schema represents an expected structure of a particular data object and validation rules.
To create a Joi schema, you can use the Joi.object() method and chain the various validation rules given by Joi to define the structure & validation requirements for your data.
For example:
const exampleSchema = Joi.object({ name: Joi.string().min(3).required(), });
The above example describes a simple Joi schema with a name attribute . The name attribute has the value of Joi.string().min(3).required() . This means that the name value will be a string, with the required minimum length of 3 characters.
Using Joi, you can chain different methods to add more validation limits for each field defined in the schema.
For example:
const userSchema = Joi.object({ email: Joi.string().email().required(), password: Joi.string().min(6).required(), age: Joi.number().min(18).optional(), employed: Joi.boolean().optional(), phone: Joi.string() .regex(/^d{3}-d{3}-d{4}$/)//"123-456-7890" .required(), address: Joi.object({ street: Joi.string().min(3).required(), city: Joi.string().min(3).required(), state: Joi.string().min(3).required(), zip: Joi.number().min(3).required(), }).required(), hobbies: Joi.array().items(Joi.string()).required(), }).options({ abortEarly: false });
userSchema defines the following binding requirements for each property:
- Email : Must be a valid email string.
- Password : Must be a string with a 6-character number.
- Age : Several options with a minimum value of 18.
- Employed : An optional boolean.
- Phone : A requested string that matches a specific regular expression (/^d{3}-d{3}-d{4}$/) .
- Address : An object representing the user's address with the following extra properties:
- Street : A required string of at least 3 characters in length.
- City : A required string of at least 3 characters in length.
- State : A required string of at least 3 characters in length.
- zip : A required string with a minimum value of 3.
- Hobbies : Required array of strings.
In addition to the limitations, userSchema sets the abortEarly option to false . By default, Joi stops running the program as soon as it encounters the first error and prints the error to the console. However, setting this option to false ensures Joi checks the entire schema and prints all errors encountered to the console.
Data validation with Joi
Create validation.js file and add userSchema code to it.
For example:
//validation.js const Joi = require("joi"); const userSchema = Joi.object({ //. }).options({ abortEarly: false }); module.exports = userSchema;
Then create middleware that intercepts the query payload and verifies them against the provided schema by adding the following code below the userSchema code .
const validationMiddleware = (schema) => { return (req, res, next) => { const { error } = schema.validate(req.body); if (error) { // Xử lý lỗi xác thực console.log(error.message); res.status(400).json({ errors: error.details }); } else { // Dữ liệu hợp lệ, tiếp tục xử lý middleware tiếp theo next(); } }; };
When a request is made, the middleware will call the schema's validate method to validate the query content. If any authentication failure occurs, the middleware sends a 400 Bad Request response with the error message retrieved from the authentication failure details.
Otherwise, if the validation succeeds without error, the middeware calls the next() function .
Finally, export the validationMiddleware and userSchema .
module.exports = { userSchema, validationMiddleware, };
Check the validation conditions
Import validationMiddleware and userSchema into the router.js file , and set up the middleware as follows:
const { validationMiddleware, userSchema } = require("./validation"); router.post("/signup", validationMiddleware(userSchema), demoHandler);
Start the application by running the command below:
node index.js
Then create an HTTP POST query to localhost:3000/signup using the test data below. You can achieve this using cURL or any other client API.
{ "email": "user@example", // Invalid email format "password": "pass", // Password length less than 6 characters "age": 15, // Age below 18 "employed": true, "hobbies": ["reading", "running"], "phone": "123-456-789", // Invalid phone number format "address": { "street": "123", "city": "Example City", "state": "Example State", "zip": 12345 } }
This query will fail and return an error object when the payload contains a lot of invalid fields, such as email, password, age, and phone number. Using the provided error object, you can handle the appropriate error.
Simplify data validation with Joi
Here's what you need to know about data validation using Joi. As you can see, it's pretty simple, isn't it? Good luck!
You should read it
- Event Loop in Node.js
- Concept of Buffer in Node.js
- Instructions for installing Node.js
- What is Node.js?
- 10 things not to do when running Node.js application
- Read the File record in Node.js
- Utility Module in Node.js
- REPL Terminal in Node.js
- NPM in Node.js
- Hello World program in Node.js
- Things to know about event-driven programming in Node.js
- Global objects in Node.js