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 dotenvThen 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_keyHash 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.