Skip to content
Santekno.com | Tech Tutorials and Trends
EN
📖 0%
22 Oct 2025 · 6 min read ·Article 114 / 125
Go

114 Building a Global Validation Middleware for Input

IH
Ihsan Arif
Writer at Santekno · Backend Engineer

114 Building a Global Validation Middleware for Input

Input validation is one of the most important parts of application development, especially once an application has grown to a large scale and receives plenty of data from many different endpoints. One clean, scalable, and maintainable approach to handling input validation is to build a global validation middleware. In this article, I’ll walk through everything about building a global validation middleware—from the concept and implementation to the best practices I use in real-world projects.


Why a Global Validation Middleware?

Imagine you have 50 REST API endpoints, and each one requires different input validation. If validation is written manually inside every handler, not only does the code become messy, but it also becomes very hard to maintain and change down the road.

With a global validation middleware, we can achieve the following:

  • Centralized validation: All validation logic is concentrated in a single layer.
  • Reusable: Validation logic only needs to be defined once and can be used across all handlers/routes.
  • Consistent error response: The error format is always consistent.
  • Easier scalability: Adding or modifying validation becomes easier.

Let’s look at the flow as a diagram using mermaid code.

MERMAID
flowchart TD
    A[Client Request] --> B(Middleware Validasi Global)
    B -- Input Valid --> C{Handler}
    B -- Input Tidak Valid --> D[Response: 400 Bad Request]
    C --> E[Response: 200 OK]

Basic Middleware Concept

Middleware is a function that runs between an incoming request and the main handler. Middleware can inspect, modify, or halt the request flow. In some frameworks such as Express.js (Node.js), middleware is already a fundamental building block. In other backend ecosystems like NestJS, Laravel, and Go Fiber, the concept of middleware is the same.

Input Validation Middleware

The goal of validation middleware is to:

  1. Receive the request body/query/params.
  2. Validate the data against the schema rules.
  3. If valid, pass control to the next handler.
  4. If invalid, return an error response in a uniform format.

Tools: Validator & Schema

For input validation, we can use libraries such as:

  • JavaScript/Node.js: Joi , Yup , Zod
  • TypeScript: Zod, Yup
  • Python: Pydantic, Marshmallow
  • Go: go-playground/validator

In this article, I’ll use Node.js + Express + Joi because it’s the most common and straightforward, but the concept can be adapted to other languages.


Basic Implementation of a Global Validation Middleware

1. Install the Validator Library

Install Express and Joi:

bash
1npm install express joi

2. Define the Validation Schema

Create the file schemas/userSchema.js

javascript
 1const Joi = require('joi');
 2
 3const userSchema = Joi.object({
 4  name: Joi.string().min(3).required(),
 5  email: Joi.string().email().required(),
 6  age: Joi.number().integer().min(18).max(100).required(),
 7});
 8
 9module.exports = {
10  userSchema
11};

3. The Global Validation Middleware

Create the file middlewares/validate.js

javascript
 1function validate(schema, property = 'body') {
 2  return function (req, res, next) {
 3    const { error } = schema.validate(req[property], { abortEarly: false });
 4    if (!error) {
 5      return next();
 6    }
 7    // API-friendly error response format
 8    const details = error.details.map(err => ({
 9      field: err.path.join('.'),
10      message: err.message
11    }));
12
13    return res.status(400).json({
14      code: 400,
15      status: "error",
16      message: "Input validation error",
17      details
18    });
19  };
20}
21
22module.exports = validate;

4. Integration in the Route

Create the file routes/user.js

javascript
 1const express = require('express');
 2const { userSchema } = require('../schemas/userSchema');
 3const validate = require('../middlewares/validate');
 4const router = express.Router();
 5
 6// The validation middleware is applied globally on this route
 7router.post('/', validate(userSchema), (req, res) => {
 8  // If we reach this point, the input is valid
 9  const user = req.body;
10  // Simulate saving the user
11  res.status(201).json({ status: "success", data: user });
12});
13
14module.exports = router;

5. Set Up the Express Server

Create the file app.js

javascript
 1const express = require('express');
 2const userRoutes = require('./routes/user');
 3const app = express();
 4
 5app.use(express.json());
 6app.use('/users', userRoutes);
 7
 8app.listen(3000, () => {
 9  console.log('Server jalan di http://localhost:3000');
10});

Simulation: Testing the Middleware

Let’s test the POST /users API scenario.

a. Invalid Input

Send:

json
1{
2  "name": "B",
3  "email": "salah-email",
4  "age": 15
5}

Response:

json
 1{
 2  "code": 400,
 3  "status": "error",
 4  "message": "Input validation error",
 5  "details": [
 6    { "field": "name", "message": "\"name\" length must be at least 3 characters long" },
 7    { "field": "email", "message": "\"email\" must be a valid email" },
 8    { "field": "age", "message": "\"age\" must be greater than or equal to 18" }
 9  ]
10}

b. Valid Input

Send:

json
1{
2  "name": "Budi",
3  "email": "budi@mail.com",
4  "age": 28
5}

Response:

json
1{
2  "status": "success",
3  "data": {
4    "name": "Budi",
5    "email": "budi@mail.com",
6    "age": 28
7  }
8}

Here’s a Coverage Simulation Table

EndpointValidation SchemaProperty CheckedOutput When Invalid
POST /usersuserSchemabody400 & field details
PUT /users/:iduserUpdateSchemabody + params.id400 & field details
POST /articlesarticleSchemabody400 & field details

Centralizing validation makes future scaling easier and keeps validation standards consistent.


Best Practices & Tips

  • Modularize schemas: Keep a separate schema file for each resource.
  • Use middleware dynamically: Middleware can be applied at the route, group, or global level whenever needed.
  • Custom error formatter: Standardize the error format for frontend developers.
  • Support for query & params: Extend the middleware to support validating req.query and req.params.
  • Testing integration: Test the middleware with both unit and integration tests.

Here’s an example of extending the middleware to support body, query, and params all at once:

javascript
 1function validateAll(schemas) {
 2  return (req, res, next) => {
 3    let allErrors = [];
 4    for (const [property, schema] of Object.entries(schemas)) {
 5      if (!schema) continue;
 6      const { error } = schema.validate(req[property], { abortEarly: false });
 7      if (error) {
 8        allErrors = allErrors.concat(
 9          error.details.map(err => ({
10            field: `${property}.${err.path.join('.')}`,
11            message: err.message
12          }))
13        );
14      }
15    }
16    if (allErrors.length === 0) {
17      return next();
18    }
19    return res.status(400).json({
20      code: 400,
21      status: "error",
22      message: "Input validation error",
23      details: allErrors
24    });
25  };
26}
27
28// Usage:
29// router.post('/users/:id', validateAll({
30//   params: idSchema,
31//   body: userSchema
32// }), (req, res) => { ... });

Closing

Input validation is the foundation of an API’s security and robustness. By building a global validation middleware, you can achieve clean code, maintainability, and consistent standardization across the entire application. Don’t let every handler rewrite its own validation—do it the way a professional engineer would: centralize it, test it, and scale it.

If you have a different approach to validation, feel free to discuss it in the comments. And remember, good middleware is middleware that won’t stress out your team six months from now!

Related Articles

💬 Comments