DEV Community

Cover image for Stop Writing Manual API Docs: Validate and Document your Express.js Fields in One Go πŸš€
Midhun G S
Midhun G S

Posted on

Stop Writing Manual API Docs: Validate and Document your Express.js Fields in One Go πŸš€

The "Double Work" Problem

As Express developers, we’ve all been there. You spend an hour perfecting your validation logic for a new endpoint:

  • You check for required fields.
  • You validate email formats.
  • You set minimum string lengths.

Then, you have to do it all over again in a Swagger file or a Postman collection so your frontend team knows how to use the API. If you change a field in the code and forget to update the docs, things break, and Slack messages start flying.

I got tired of this manual sync, so I built expressjs-field-validator.

What is expressjs-field-validator?

It’s a lightweight middleware designed to be the "single source of truth" for your request validation and your API documentation.

Key Features:

  • Schema-based Validation: Define rules in a clean object.
  • Automatic Doc Generation: It reads your schemas and generates documentation for you.
  • Zero Bloat: Minimal dependencies to keep your project fast.

See it in Action

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

const app = express();
app.use(express.urlencoded({ extended: true }));
app.use(express.json());

// ─── 1. Basic required field validation ──────────────────────────────────────
// POST /users  { "name": "John", "email": "john@example.com" }
app.post('/users',
  validateBody().isToBeRejected().sendErrorCode(422).addParams([
    param('name').isRequired(),
    param('email').isRequired().isEmail(),
  ]),
  documentResponse({
    201: {
      description: 'User created successfully',
      body: { message: 'User created', data: { id: 1, name: 'John', email: 'john@example.com' } }
    },
    422: {
      description: 'Validation failed',
      body: { error: 'email is required' }
    }
  }),
  (req, res) => {
    res.status(201).send({ message: 'User created', data: req.body });
  }
);

// ─── 2. Number, range, and length validation ─────────────────────────────────
// POST /products  { "title": "Widget", "price": 25, "sku": "AB123" }
app.post('/products',
  validateBody().isToBeRejected().sendErrorCode(422).debug(true).addParams([
    param('title').isRequired().minimumLength(3).maximumLength(100),
    param('price').isRequired().isNumber().minimumNumber(1).maximumNumber(99999),
    param('sku').isRequired().minimumLength(3).maximumLength(20),
  ]),
  documentResponse({
    201: {
      description: 'Product created',
      body: { message: 'Product created', data: { title: 'Widget', price: 25, sku: 'AB123' } },
      headers: { 'X-Product-Id': '12345' }
    },
    422: {
      description: 'Validation error',
      body: { error: 'price must be between 1 and 99999' }
    }
  }),
  (req, res) => {
    res.status(201).send({ message: 'Product created', data: req.body });
  }
);

// ─── 3. Boolean, date, includes/excludes, and convertToFormat ────────────────
// POST /events  { "title": "Meetup", "date": "15/03/2026", "type": "online", "isPublic": true }
//   β†’ date is validated as DD/MM/YYYY then converted to YYYY-MM-DD for storage
app.post('/events',
  validateBody().isToBeRejected().addParams([
    param('title').isRequired(),
    param('date').isRequired().isDate().dateFormat('DD/MM/YYYY').convertToFormat('YYYY-MM-DD'),
    param('type').isRequired().shouldInclude(['online', 'offline', 'hybrid']),
    param('isPublic').isBoolean(),
  ]),
  (req, res) => {
    // req.body.date is now in YYYY-MM-DD format
    res.status(201).send({ message: 'Event created', data: req.body });
  }
);
// ─── Start server ────────────────────────────────────────────────────────────
const PORT = 3000;

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

app.listen(PORT, (err) => {
  if (err) {
    console.error(`Error starting server: ${err}`);
    return;
  }
  console.log(`Server is listening on http://localhost:${PORT}`);
  console.log(`API Docs available at: ./docs/api-docs.html`);
});

Enter fullscreen mode Exit fullscreen mode

Why I Open Sourced This

I wanted a tool that didn't require a 200-page manual to understand. This package is built for developers who want to move fast without sacrificing code quality or documentation.

What is next

  • More export formats (OpenAPI 3.0, Postman Collections, etc.).
  • More field validators like address, credit card etc.

Contributing & Feedback
This is an evolving project, and I’d love the community’s input.

Check it out on NPM: expressjs-field-validator
Contribute on GitHub: https://github.com/gsmithun4/expressjs-field-validator

Top comments (0)