Express.js REST API Development

Build professional REST APIs with Express.js. Learn about API design, versioning, documentation, rate limiting, and best practices.

Overview

Building RESTful APIs with Express.js requires understanding REST principles, proper response handling, and API best practices.

REST Principles

REST (Representational State Transfer) APIs follow these principles: - Use HTTP methods appropriately (GET, POST, PUT, DELETE) - Stateless communication - Consistent resource naming - Proper use of status codes

API Response Format

Consistent response formats make your API easier to use. Include status codes, data, and error messages in a predictable structure.

API Versioning

Version your API to make breaking changes without affecting existing clients. Common approaches include URL versioning (/v1/users) or header versioning.

Documentation

Good documentation is essential. Use tools like Swagger/OpenAPI to generate interactive documentation.

Code Examples

RESTful Resource Controller

controllers/userController.js
// User Controller with CRUD operations
const User = require('../models/User');

exports.getAll = async (req, res) => {
  const { page = 1, limit = 10, sort = '-createdAt' } = req.query;

  const users = await User.find()
    .sort(sort)
    .skip((page - 1) * limit)
    .limit(Number(limit));

  const total = await User.countDocuments();

  res.json({
    status: 'success',
    results: users.length,
    pagination: {
      page: Number(page),
      limit: Number(limit),
      total,
      pages: Math.ceil(total / limit)
    },
    data: users
  });
};

exports.getOne = async (req, res) => {
  const user = await User.findById(req.params.id);
  if (!user) {
    return res.status(404).json({
      status: 'fail',
      message: 'User not found'
    });
  }
  res.json({ status: 'success', data: user });
};

exports.create = async (req, res) => {
  const user = await User.create(req.body);
  res.status(201).json({ status: 'success', data: user });
};

API Versioning

routes/index.js
const express = require('express');
const v1Router = require('./v1');
const v2Router = require('./v2');

const app = express();

// Version 1 API
app.use('/api/v1', v1Router);

// Version 2 API with new features
app.use('/api/v2', v2Router);

// Default to latest version
app.use('/api', v2Router);

// Version info endpoint
app.get('/api/versions', (req, res) => {
  res.json({
    versions: ['v1', 'v2'],
    current: 'v2',
    deprecated: ['v1']
  });
});

Rate Limiting

middleware/rateLimiter.js
const rateLimit = require('express-rate-limit');

// General API rate limiter
const apiLimiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests
  message: {
    status: 'error',
    message: 'Too many requests, please try again later'
  },
  standardHeaders: true,
  legacyHeaders: false,
});

// Stricter limiter for auth routes
const authLimiter = rateLimit({
  windowMs: 60 * 60 * 1000, // 1 hour
  max: 5, // 5 attempts per hour
  message: {
    status: 'error',
    message: 'Too many login attempts, try again in an hour'
  }
});

app.use('/api/', apiLimiter);
app.use('/api/auth/login', authLimiter);

Frequently Asked Questions

What HTTP status codes should I use?
200 for success, 201 for created, 204 for no content, 400 for bad request, 401 for unauthorized, 403 for forbidden, 404 for not found, 500 for server error. Be consistent and use codes that accurately reflect the response.
How should I handle API pagination?
Use query parameters like ?page=1&limit=10 or cursor-based pagination for large datasets. Always return pagination metadata including total count, current page, and total pages.
Should I use PUT or PATCH for updates?
PUT replaces the entire resource and requires sending all fields. PATCH allows partial updates with only changed fields. Use PATCH for most update operations as it's more efficient and flexible.

Need expert help with Express.js?

Our team at Slashdev.io builds production-ready Express.js applications.