expressjs-field-validator

A powerful, lightweight Express.js middleware for validating request fields (body, query, params, headers) with a fluent, chainable API. Zero dependencies, full TypeScript support, and automatic API documentation generation.

Key Features:

🛠️ Config Builder Tool - Generate validation configs visually!

Config Builder Screenshot

Reliability Rating Bugs code_smells coverage duplicated_lines_density ncloc sqale_rating alert_status security_rating security_rating sqale_index vulnerabilities

Installation

$ npm install expressjs-field-validator

How To Use

 const { 
  validateBody,
  validateParam,
  validateQuery,
  param,
} = require('expressjs-field-validator');
router.post('/users/:id',
validateParam().addParams([
  param('id').isNumber()
]),
validateBody().addParams([
  param('userId').isNumber()
]),
validateQuery().addParams([
  param('userName').isRequired()
]),
validateHeader().addParams([
  param('Authorization').isRequired()
]),
(req, res, next) => {

  // Main Service Here

});

Getting Started

Defining a Field

Use param(<field Name>) to define a field.

param('userName').isRequired()

Defines a field userName which is mandatory.

Available Options

isRequired()

Field is mandatory

isArray()

Expects array

isObject()

Expects object

isNumber()

Expects number

isEmail()

Expects email

isUUID()

Expects UUID string

isMatching(regex)

Validates that the field value matches the provided regular expression. The value is coerced to a string before testing.

// Using a RegExp object
param('code').isRequired().isMatching(/^[A-Z]+-\d+$/)

// Using a pattern string
param('postalCode').isRequired().isMatching('^\\d{5}$')
isBoolean()

Expects boolean value

isDate()

Expects a date with default format YYYY-MM-DD

dateFormat(format)

Supported formats are the same as dateFormat:

YYYY-MM-DD
DD-MM-YYYY
MM-DD-YYYY
YYYY/MM/DD
DD/MM/YYYY
MM/DD/YYYY

If the specified format is not in the supported list, the conversion is silently skipped and the original value is preserved.

// Convert DD/MM/YYYY input to YYYY-MM-DD
param('birthDate').isDate().dateFormat('DD/MM/YYYY').convertToFormat('YYYY-MM-DD')
// Input: "25/12/2024" → Value after validation: "2024-12-25"

// Convert with default source format (YYYY-MM-DD) to DD-MM-YYYY
param('eventDate').isDate().convertToFormat('DD-MM-YYYY')
// Input: "2024-12-25" → Value after validation: "25-12-2024"
minimumNumber(min)

Available Options

isToBeRejected()

Defines the validation failure event - Server returns http status code set via sendErrorCode (default 422), :heavy_exclamation_mark: will not proceed to the next middleware Response body

{
    "error": [
        {
            "location": "body.sort",
            "param": "sort",
            "message": "Invalid Field Error"
        }
    ]
}
isToBeForwarded()

Defines the validation failure event - Error is set to request.locals.data and error code to request.locals.statusCode, :white_check_mark: will proceed to the next middleware Error object Response body

{
    "error": [
        {
            "location": "body.sort",
            "param": "sort",
            "message": "Invalid Field Error"
        }
    ]
}
checkService
  const { checkService } = require('expressjs-field-validator');

Pass middleware to checkService, which must be skipped if isToBeForwarded enabled and validation errors are found

router.get('/users/:id',
validateBody().isToBeForwarded().sendErrorCode(500).debug(false).addParams([
  param('id').isRequired().isNumber()
]),
checkService((req, res, next) => {

  // This middleware is skipped if id is empty or not a number
  
}),
(req, res, next) => {

  // This middleware Will not be skipped, error data will be availble here - req.locals.data and status code - request.locals.statusCode here 
  
});
skipService

manually invoke forward mode, if this is set from any middleware, the middlewares wrapped inside checkService won’t be executed

 const { skipService } = require('expressjs-field-validator');
router.get('/users/:id',
(req, res, next) => {

  skipService(req, 'SOME-ERROR');
  next();
  
}),
 
checkService((req, res, next) => {

  // This middleware is skipped
  
}),
(req, res, next) => {

  // This middleware Will not be skipped, error data will be availble here - req.locals.data and status code - request.locals.statusCode here 
  
});
sendErrorCode(errorCode)

This is a convenience method to avoid adding removeIfEmpty() to each field individually.

// Instead of adding removeIfEmpty() to each field:
validateBody().addParams([
  param('name').removeIfEmpty(),
  param('email').removeIfEmpty(),
  param('phone').removeIfEmpty(),
])

// You can add it once at the top level:
validateBody().removeIfEmpty().addParams([
  param('name'),
  param('email'),
  param('phone'),
])

Note: Field-level removeIfEmpty() can still be used when you only want to remove specific fields.

cleanUp()

Removes any keys from the request that are not declared in the validation params. This sanitizes the input to only include explicitly defined fields. Works recursively on nested objects and arrays.

// Request body: { name: "John", email: "john@test.com", hackAttempt: "malicious", extra: "data" }
validateBody().cleanUp().addParams([
  param('name').isRequired(),
  param('email').isEmail(),
])
// After validation, req.body will only contain: { name: "John", email: "john@test.com" }
// The "hackAttempt" and "extra" keys are removed

This is useful for:

Note: cleanUp() also works recursively on nested objects and arrays defined with addChild() or addChildren().

addParams(paramList)

// Main Service Here

});


## API Documentation Generator

Automatically generate beautiful HTML documentation for your API endpoints. The generator extracts all routes with validation middlewares and creates an interactive documentation page.

![API Documentation Screenshot](/expressjs-field-validator/assets/api-docs-screenshot.png)

### documentResponse()

Use `documentResponse()` to document the possible responses for an endpoint. This is a pass-through middleware that attaches response metadata for documentation generation. Place it after validation middleware but before your route handler.

```js
const { documentResponse } = require('expressjs-field-validator');

app.post('/users',
  validateBody().isToBeRejected().addParams([
    param('name').isRequired(),
    param('email').isRequired().isEmail(),
  ]),
  documentResponse({
    201: { 
      description: 'User created successfully',
      body: { message: 'User created', data: { id: 1, name: 'John' } },
      headers: { 'X-Request-Id': 'uuid' }
    },
    422: { 
      description: 'Validation failed',
      body: { error: 'Validation failed', details: [] }
    }
  }),
  (req, res) => res.status(201).send({ message: 'User created' })
);

Response Configuration Options

Option Type Description
description string Description of the response
body any Sample response body
headers object Response headers as key-value pairs

Note: Status codes must be valid HTTP status codes (100-599). The middleware throws an error for invalid status codes.

Usage

const express = require('express');
const { 
  validateBody, 
  validateQuery, 
  param, 
  generateDocs 
} = require('expressjs-field-validator');

const app = express();

// Define your routes with validation
app.post('/users',
  validateBody().isToBeRejected().addParams([
    param('name').isRequired(),
    param('email').isRequired().isEmail(),
  ]),
  (req, res) => res.status(201).send({ message: 'User created' })
);

app.get('/users/:id',
  validateQuery().isToBeRejected().addParams([
    param('include').shouldInclude(['profile', 'settings', 'posts']),
  ]),
  (req, res) => res.send({ user: {} })
);

// Generate documentation AFTER all routes are registered
generateDocs(app, {
  title: 'My API',
  version: '1.0.0',
  outputDir: './docs',
  filename: 'api-docs.html'
});

app.listen(3000);

Options

Option Type Default Description
outputDir string './docs' Output directory for the generated HTML file
filename string 'api-docs.html' Output filename
title string 'API Documentation' Documentation title
version string - API version to display

Features

Hosting API Documentation

You can serve the generated HTML documentation using Express static middleware:

const express = require('express');
const path = require('path');
const { generateDocs, validateBody, param } = require('expressjs-field-validator');

const app = express();
app.use(express.json());

// Define your API routes
app.post('/users',
  validateBody().isToBeRejected().addParams([
    param('name').isRequired(),
    param('email').isRequired().isEmail(),
  ]),
  (req, res) => res.status(201).send({ message: 'User created' })
);

// Generate documentation
generateDocs(app, {
  title: 'My API',
  version: '1.0.0',
  outputDir: './docs'
});

// Serve the documentation at /api-docs
app.use('/api-docs', express.static(path.join(__dirname, 'docs')));

app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
  console.log('API Docs available at http://localhost:3000/api-docs/api-docs.html');
});

Alternative: Serve docs at root path

// Serve docs at /docs endpoint
app.use('/docs', express.static(path.join(__dirname, 'docs')));

// Or redirect /docs to the HTML file directly
app.get('/docs', (req, res) => {
  res.sendFile(path.join(__dirname, 'docs', 'api-docs.html'));
});

Production Tips:

// Only serve docs in non-production
if (process.env.NODE_ENV !== 'production') {
  app.use('/api-docs', express.static(path.join(__dirname, 'docs')));
}

Migration Guide

Migrating from v3.x to v4.x

Breaking Changes

1. end() removed from field definitions

end() is no longer required (or available) when defining fields with param().

- param('userName').isRequired().end()
+ param('userName').isRequired()
- param('field21').isNumber().isRequired().end()
+ param('field21').isNumber().isRequired()
2. done() removed from validation middleware

done() is no longer required (or available) when creating validation middleware.

- validateBody().addParams([
-   param('field1').isRequired().end(),
- ]).done()
+ validateBody().addParams([
+   param('field1').isRequired(),
+ ])
- validateParam().isToBeRejected().sendErrorCode(500).addParams([
-   param('id').isNumber().end(),
- ]).done()
+ validateParam().isToBeRejected().sendErrorCode(500).addParams([
+   param('id').isNumber(),
+ ])
Full Before / After Example

v3.x

router.post('/users/:id',
validateParam().addParams([
  param('id').isNumber().end()
]).done(),
validateBody().isToBeRejected().sendErrorCode(500).addParams([
  param('name').isRequired().end(),
  param('email').isEmail().end(),
  param('role').shouldInclude(['admin', 'user']).end(),
]).done(),
(req, res, next) => {
  // Main Service
});

v4.x

router.post('/users/:id',
validateParam().addParams([
  param('id').isNumber()
]),
validateBody().isToBeRejected().sendErrorCode(500).addParams([
  param('name').isRequired(),
  param('email').isEmail(),
  param('role').shouldInclude(['admin', 'user']),
]),
(req, res, next) => {
  // Main Service
});

New Features

The following features are new in v4.x:

defaultValue(value)

Sets a default value for a field when the value is undefined, null, or ''.

param('status').defaultValue('active')
param('count').isNumber().defaultValue(0)
removeIfEmpty()

Removes the field key from the request if the value is empty.

param('notes').removeIfEmpty()
param('tags').isArray().removeIfEmpty()
convertToFormat(format)

Converts a validated date to a different format. The original value in the request is replaced with the converted value.

param('birthDate').isDate().dateFormat('DD/MM/YYYY').convertToFormat('YYYY-MM-DD')