DEV Community

Cover image for Building a Robust Express API with TypeScript and express-validator
William Onyejiaka
William Onyejiaka

Posted on

Building a Robust Express API with TypeScript and express-validator

Introduction

When you build an API, users send data to your backend — things like email addresses, usernames, and passwords. But what happens if that data is invalid or missing?

That’s where validation comes in. Validation means checking that the incoming data is correct before using it.

In this guide, you’ll learn how to use express-validator in a TypeScript + Express project to validate and sanitize user input. We’ll go step by step, so even if you’re new to TypeScript or Express, you’ll be able to follow along easily.


Why You Need express-validator

express-validator is a middleware that makes input validation simple and readable.

Instead of manually checking if req.body.email looks like an email or if the password is long enough, you just describe the rules, and express-validator does the checking for you.

For example:

body("email").isEmail().withMessage("Please enter a valid email");
Enter fullscreen mode Exit fullscreen mode

This line says: “The email field must be a valid email.”
If it’s not, express-validator automatically collects the error for you.


Project Structure

Before diving into the code, here’s what your project structure will look like when we’re done:

express-ts-validator/
│
├── src/
│   ├── server.ts
│   ├── routes/
│   │   └── user.ts
│   ├── middlewares/
│   │   └── validate.ts
│
├── tsconfig.json
├── package.json

Enter fullscreen mode Exit fullscreen mode

What each file does:

  • server.ts – sets up Express, middlewares,routes and starts the Express server.

  • routes/user.ts – holds all user-related routes (like registration).

  • middlewares/validate.ts – reusable function to handle validation errors.

  • tsconfig.json – TypeScript configuration file.


Setting Up the Project

Step 1: Initialize the Project

Create a new folder and initialize npm.

mkdir express-ts-validator
cd express-ts-validator
npm init -y
Enter fullscreen mode Exit fullscreen mode

Step 2: Install Dependencies

npm install express express-validator
npm install --save-dev nodemon typescript ts-node @types/express @types/node
Enter fullscreen mode Exit fullscreen mode

Step 3: Configure TypeScript

Initialize TypeScript configuration:

npx tsc --init
Enter fullscreen mode Exit fullscreen mode

Update tsconfig.json to include:

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "rootDir": "./src",
    "outDir": "./dist",
    "esModuleInterop": true,
    "strict": true
  }
}
Enter fullscreen mode Exit fullscreen mode

This tells TypeScript how to compile your code.


Step 4: Configure scripts

Update package.json with:

"scripts": {
  "start": "node dist/server.js",
  "build": "tsc p .",
  "dev": "nodemon src/server.ts"
}
Enter fullscreen mode Exit fullscreen mode

Setting Up Express with TypeScript

Create a basic Express server in src/server.ts:

import express, { Request, Response } from "express";

const PORT = 3000;
const app = express();

// Enable URL-encoded form data parsing
app.use(express.urlencoded({ extended: true }));

// Middleware to parse JSON
app.use(express.json());

app.get("/", (req: Request, res: Response) => {
  res.json({ message: "Hello from Express with TypeScript" });
});

// Start server
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Enter fullscreen mode Exit fullscreen mode

Run your app:

npm run dev
Enter fullscreen mode Exit fullscreen mode

Then visit:

http://localhost:3000
Enter fullscreen mode Exit fullscreen mode

You should see:

{ "message": "Hello, Express with TypeScript!" }
Enter fullscreen mode Exit fullscreen mode

Validating Input with express-validator

Let’s make a POST endpoint for user registration.

We’ll check:

  • Email is valid.

  • Password is at least 6 characters.

  • Username is not empty and has at least 3 characters.

  • Create a new folder src/routes/ and inside it a file user.ts.

src/routes/user.ts

import { Router, Request, Response } from "express";
import { body, validationResult } from "express-validator";

const router = Router();

router.post(
  "/register",
  [
    body("email").isEmail().withMessage("Please enter a valid email"),
    body("password")
      .isLength({ min: 6 })
      .withMessage("Password must be at least 6 characters long"),
    body("username")
      .notEmpty()
      .isLength({ min: 3 })
      .withMessage("Username must be at least 3 characters long")
  ],
  (req: Request, res: Response) => {
    const errors = validationResult(req);

    if (!errors.isEmpty()) {
      return res.status(400).json({ errors: errors.array() });
    }

    const { email, username } = req.body;
    res.json({ message: "User registered successfully", data: { email, username } });
  }
);

export default router;

Enter fullscreen mode Exit fullscreen mode

Then, connect the route to your app in src/server.ts:

import express, { Request, Response } from "express";
import userRoutes from "./routes/user";

const PORT = 3000;
const app = express();

// Enable URL-encoded form data parsing
app.use(express.urlencoded({ extended: true }));

// Middleware to parse JSON
app.use(express.json());

// Connecting user route to your app
app.use("/api/users", userRoutes);

app.get("/", (req: Request, res: Response) => {
  res.json({ message: "Hello from Express with TypeScript" });
});

// Start server
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));
Enter fullscreen mode Exit fullscreen mode

Now if you send a POST request,using a tool like Postman, to:

http://localhost:3000/api/users/register
Enter fullscreen mode Exit fullscreen mode

with invalid data, you’ll get clear error messages from express-validator.


Handling Validation Errors

Right now, every route has its own validationResult logic.
If your project grows, that can get repetitive.

Let’s move that logic into its own file so we can reuse it anywhere.


Creating a Reusable Validation Middleware

Create a new folder src/middlewares/ and add a file called validate.ts.

src/middlewares/validate.ts

import { Request, Response, NextFunction } from "express";
import { validationResult } from "express-validator";

export function validate(req: Request, res: Response, next: NextFunction) {
  const errors = validationResult(req);
  if (!errors.isEmpty()) {
    return res.status(400).json({ errors: errors.array() });
  }
  next();
}
Enter fullscreen mode Exit fullscreen mode

Now, you can import and use it anywhere like this:

import { Router,Request,Response } from "express";
import { body } from "express-validator";
import { validate } from "./../middlewares/validate"; // Importing the validate middleware

const router = Router();

router.post(
  "/register",
  [
    body("email").isEmail().withMessage("Invalid email"),
    body("password").isLength({ min: 6 }).withMessage("Password too short"),
    body("username").notEmpty().withMessage("Username is required")
  ],
  validate,// Registering the middleware in the register route
  (req: Request, res: Response) => {
    const { email, username } = req.body;
    res.json({ message: "User registered successfully", data: { email, username } });
  }
);

Enter fullscreen mode Exit fullscreen mode

This approach keeps your routes clean and easy to maintain.


Wrapping Up

You’ve just built a clean, type-safe Express API that validates user input with express-validator.

Here’s what you learned:

  • How to set up an Express + TypeScript project.

  • How to validate user input using express-validator.

  • How to handle validation errors using a reusable middleware.

From here, you can improve your project by:

  • Adding sanitization (e.g., trimming whitespace).

  • Writing custom validators (like checking if an email already exists).

  • Organizing routes and validators into separate files for bigger apps.

By learning this pattern early, you’re building a solid foundation for writing secure and maintainable backend applications.

The code for this blog is here.

Top comments (0)