Building a CRUD API (Node + Express)
A CRUD API implements the four fundamental data operations: Create, Read, Update, Delete. This lesson walks you through building a production-quality CRUD API for a products resource using Node.js, Express, and in-memory data.
1 Project Structure
Shell — Project Layout
my-rest-api/
src/
routes/
products.js # Route handlers
middleware/
validate.js # Input validation
errors.js # Error handler
models/
Product.js # Data model
app.js # Express setup
server.js # Entry point
package.json
2 Complete CRUD Route Handler
JavaScript — routes/products.js
const express = require("express");
const router = express.Router();
let products = [
{ id: 1, name: "Mechanical Keyboard", price: 8999, category: "electronics", inStock: true }
];
let nextId = 2;
// GET /products — list with optional filtering
router.get("/", (req, res) => {
let result = [...products];
const { category, minPrice, maxPrice, inStock } = req.query;
if (category) result = result.filter(p => p.category === category);
if (minPrice) result = result.filter(p => p.price >= Number(minPrice));
if (maxPrice) result = result.filter(p => p.price <= Number(maxPrice));
if (inStock !== undefined) result = result.filter(p => p.inStock === (inStock === "true"));
res.json({ success: true, data: result, count: result.length });
});
// GET /products/:id
router.get("/:id", (req, res) => {
const product = products.find(p => p.id === Number(req.params.id));
if (!product) return res.status(404).json({ success: false, error: "Product not found" });
res.json({ success: true, data: product });
});
// POST /products — create
router.post("/", (req, res) => {
const { name, price, category, inStock = true } = req.body;
if (!name || price === undefined || !category) {
return res.status(400).json({ success: false, error: "name, price, category are required" });
}
const product = { id: nextId++, name, price, category, inStock };
products.push(product);
res.status(201).json({ success: true, data: product });
});
// PATCH /products/:id — partial update
router.patch("/:id", (req, res) => {
const idx = products.findIndex(p => p.id === Number(req.params.id));
if (idx === -1) return res.status(404).json({ success: false, error: "Product not found" });
products[idx] = { ...products[idx], ...req.body, id: products[idx].id };
res.json({ success: true, data: products[idx] });
});
// DELETE /products/:id
router.delete("/:id", (req, res) => {
const idx = products.findIndex(p => p.id === Number(req.params.id));
if (idx === -1) return res.status(404).json({ success: false, error: "Product not found" });
products.splice(idx, 1);
res.status(204).send();
});
module.exports = router;
3 Code Challenge
Challenge: Add a
GET /products/stats endpoint (placed before /:id to avoid param conflict) that returns the total count, average price, and count per category of all products.