Skip to content

Express REST API — Complete Guide to Building RESTful APIs with Express

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Express REST API. We cover key concepts, practical examples, and best practices to help you master this topic.

Building REST APIs with Express involves designing resource-based endpoints, handling CRUD operations, validating requests, and returning structured JSON responses.

What You'll Learn

By the end of this tutorial, you'll design RESTful resources, implement CRUD endpoints, add pagination and filtering, validate input, handle errors consistently, and version your API.

Why REST APIs Matter

REST is the dominant API architecture. Most web and mobile applications communicate with backend services through REST APIs. Mastering REST API design is essential for backend development.

Real-World Use

A mobile banking app communicates with a REST API for transactions, account balances, and user management. The API serves both iOS and Android clients from a single Express backend.

REST API Learning Path

flowchart LR
  A[Security] --> B[REST API]
  B --> C[GraphQL]
  C --> D[WebSocket]
  D --> E[Authentication]
  A --> F{You Are Here}
  style F fill:#f90,color:#fff

RESTful Resource Design

import express from "express";
const app = express();
app.use(express.json());
let products = [
  { id: 1, name: "Laptop", price: 999.99, inStock: true },
  { id: 2, name: "Mouse", price: 29.99, inStock: true }
];
let nextId = 3;

CRUD Endpoints

// GET all
app.get("/api/products", (req, res) => res.json(products));
// GET one
app.get("/api/products/:id", (req, res) => {
  const product = products.find(p => p.id === Number(req.params.id));
  if (!product) return res.status(404).json({ error: "Product not found" });
  res.json(product);
});
// POST create
app.post("/api/products", (req, res) => {
  const { name, price, inStock } = req.body;
  const product = { id: nextId++, name, price, inStock };
  products.push(product);
  res.status(201).json(product);
});
// PUT update
app.put("/api/products/:id", (req, res) => {
  const idx = products.findIndex(p => p.id === Number(req.params.id));
  if (idx === -1) return res.status(404).json({ error: "Product not found" });
  products[idx] = { ...products[idx], ...req.body, id: products[idx].id };
  res.json(products[idx]);
});
// DELETE
app.delete("/api/products/:id", (req, res) => {
  const idx = products.findIndex(p => p.id === Number(req.params.id));
  if (idx === -1) return res.status(404).json({ error: "Product not found" });
  products.splice(idx, 1);
  res.status(204).send();
});

Pagination and Filtering

app.get("/api/products", (req, res) => {
  const { page = 1, limit = 10, sort, name } = req.query;
  let result = [...products];
  if (name) result = result.filter(p => p.name.toLowerCase().includes(name.toLowerCase()));
  if (sort === "price") result.sort((a, b) => a.price - b.price);
  const start = (page - 1) * limit;
  const paginated = result.slice(start, start + Number(limit));
  res.json({
    data: paginated,
    pagination: { page: Number(page), limit: Number(limit), total: result.length }
  });
});

Error Handling Wrapper

const asyncHandler = (fn) => (req, res, next) =>
  Promise.resolve(fn(req, res, next)).catch(next);

app.get("/api/products/:id", asyncHandler(async (req, res) => {
  const product = await db.findProduct(req.params.id);
  if (!product) {
    return res.status(404).json({ error: "Product not found" });
  }
  res.json(product);
}));

API Versioning

// URL versioning
app.use("/api/v1", v1Router);
app.use("/api/v2", v2Router);

// Header versioning
app.get("/api/products", (req, res) => {
  if (req.headers["accept-version"] === "2") {
    return v2Response(res);
  }
  return v1Response(res);
});

Common Mistakes

1. Using GET for Mutations

GET requests should be idempotent and safe. Use POST for creation, PUT/PATCH for updates, DELETE for deletion.

2. Inconsistent Error Responses

Return errors in a consistent format: { error: string, statusCode: number, details?: any }. Never return HTML or plain text for errors.

3. Not Validating Request Body

Users send invalid or missing data. Validate request body before processing. Return clear validation errors.

4. Exposing Internal IDs

Sequential IDs (1, 2, 3) reveal how many resources exist. Use UUIDs if enumeration is a concern.

5. No Rate Limiting on API Routes

Public APIs without rate limiting are vulnerable to abuse. Apply rate limiting middleware to API routes.

Practice Questions

1. What HTTP methods correspond to CRUD operations?

POST (Create), GET (Read), PUT/PATCH (Update), DELETE (Delete).

2. How do you implement pagination in a REST API?

Accept page and limit query parameters. Slice the result array accordingly and return pagination metadata.

3. Why use PUT vs PATCH?

PUT replaces the entire resource. PATCH applies partial modifications. Use PUT when sending the full object, PATCH for partial updates.

4. What status codes should a REST API return?

201 for creation, 200 for success, 204 for deletion, 400 for bad request, 404 for not found, 500 for server error.

5. Challenge: Build a complete REST API for a todo list with CRUD, filtering, and pagination.

app.get("/api/todos", (req, res) => {
  let result = todos;
  if (req.query.completed) result = result.filter(t => t.completed === (req.query.completed === "true"));
  const page = Number(req.query.page) || 1;
  const limit = Number(req.query.limit) || 10;
  const start = (page - 1) * limit;
  res.json({ data: result.slice(start, start + limit), total: result.length, page, limit });
});

FAQ

What is the difference between REST and RESTful?

REST is the architectural style. RESTful means the API follows REST principles: stateless, resource-based, using HTTP methods properly.

Should I use plural or singular resource names?

Plural: /api/users, /api/products. Consistent naming is more important than the choice.

How do I handle nested resources?

Use nested routes: GET /api/users/:userId/posts/:postId. Keep nesting to 2-3 levels max.

What is HATEOAS?

Hypermedia As The Engine Of Application State. Responses include links to related resources. Rarely implemented in practice.

How do I document a REST API?

Use OpenAPI/Swagger. Define all endpoints, request/response schemas, and authentication in a spec file.

Mini Project: Products REST API

Build a complete REST API for managing products with validation and error handling.

import express from "express";
const app = express();
app.use(express.json());
let products = [];
let nextId = 1;
app.get("/api/products", (req, res) => res.json(products));
app.get("/api/products/:id", (req, res) => {
  const p = products.find(x => x.id === Number(req.params.id));
  if (!p) return res.status(404).json({ error: "Not found" });
  res.json(p);
});
app.post("/api/products", (req, res) => {
  const { name, price } = req.body;
  if (!name || price == null) return res.status(400).json({ error: "Name and price required" });
  if (price < 0) return res.status(400).json({ error: "Price must be positive" });
  products.push({ id: nextId++, name, price });
  res.status(201).json(products.at(-1));
});
app.listen(3000);

What's Next

GraphQL Express WebSocket SocketIO Node.js Authentication

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro