Express REST API — Complete Guide to Building RESTful APIs with Express
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
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