How to Build a Secure REST API with Node.js and Express


Introduction

Building a secure API is a core skill in backend web development. In this Express.js tutorial, you will learn how to create a simple Node.js REST API and apply practical protections such as security headers, input validation, rate limiting, and token-based authentication. The goal is not to build a large app, but to understand the essentials of RESTful API security.

1. Set Up the Project

Start by creating a new Node.js project and installing the required packages:

npm init -y
npm install express helmet cors express-rate-limit jsonwebtoken bcryptjs dotenv

Then create a basic server:

const express = require('express');
const helmet = require('helmet');
const cors = require('cors');
const rateLimit = require('express-rate-limit');

const app = express();
app.use(helmet());
app.use(cors());
app.use(express.json());
app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 }));

app.listen(3000, () => console.log('API running on port 3000'));

This setup gives your API a secure baseline. helmet adds HTTP security headers, cors controls cross-origin access, and rate limiting helps reduce abuse.

2. Create a Simple Protected API

A common pattern in any Node.js REST API is to provide a login route and protect application routes with JSON Web Tokens (JWT).

Login Route

const jwt = require('jsonwebtoken');

app.post('/login', (req, res) => {
  const { username } = req.body;
  const token = jwt.sign({ username }, process.env.JWT_SECRET, { expiresIn: '1h' });
  res.json({ token });
});

In a real app, verify the user credentials from a database and compare hashed passwords with bcryptjs.

Auth Middleware

function auth(req, res, next) {
  const header = req.headers.authorization;
  const token = header && header.split(' ')[1];
  if (!token) return res.sendStatus(401);

  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) return res.sendStatus(403);
    req.user = user;
    next();
  });
}

Now protect a route:

app.get('/profile', auth, (req, res) => {
  res.json({ message: `Welcome ${req.user.username}` });
});

3. Validate Input Carefully

Input validation is a major part of RESTful API security. Never trust incoming data from clients. At minimum, check required fields, data types, and allowed values before processing requests.

app.post('/users', (req, res) => {
  const { email } = req.body;
  if (!email || !email.includes('@')) {
    return res.status(400).json({ error: 'Valid email is required' });
  }
  res.status(201).json({ message: 'User created' });
});

For production systems, consider a dedicated validation library such as Joi or express-validator to keep validation rules clean and reusable.

4. Apply Security Best Practices

This Express.js tutorial covers the basics, but secure APIs also depend on good operational habits:

Use Environment Variables

Store secrets like JWT keys in .env, never directly in code.

JWT_SECRET=your_strong_secret_key

Hash Passwords

If you store user accounts, hash passwords before saving them.

const bcrypt = require('bcryptjs');
const hashedPassword = await bcrypt.hash(password, 10);

Limit CORS and Error Details

Allow only trusted origins and avoid exposing internal error messages to clients. Attackers often use verbose errors to map your system.

5. Test Your API

Use tools like Postman or curl to test login, protected routes, invalid input, and rate limits. Security is not a one-time feature; it should be checked continuously as your API grows.

Conclusion

A secure API starts with strong defaults and simple patterns. In this guide, you learned how to build a small Node.js REST API with Express, protect routes with JWT, validate input, and reduce common risks with headers and rate limiting. These are foundational practices for modern backend web development and a solid starting point for improving RESTful API security in any Express-based service.