Building a CRUD API (Node + Express)

🔗 REST API Lesson 8 Intermediate

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.