4.1Building a complete REST API (CRUD)
The essentials in 30 seconds
- REST = resources + HTTP methods: the URL names the resource in the plural (
/users), the method carries the action (GET read, POST create, PUT update, DELETE delete). - Structure in layers (MVC): routes (URLs) → controllers (logic) → models (data);
express.Router()groups routes by resource. - Salim's reflex: return JSON with the right code (201 after creation, 404 if missing, 400 if invalid) and version (
/api/v1/).
Running case — Salim builds the products API in Algiers
Salim, a junior developer in a startup in Algiers, must expose the products catalogue to the front end: list, show one product, create, update, delete. He first designs his REST endpoints (GET /api/v1/produits, GET /api/v1/produits/:id, POST, PUT, DELETE), separates routes, controllers and data, and groups the routes in an express.Router() mounted on the app. When the front end calls his API, each response arrives as JSON with the right status code. First milestone of the thread: a predictable API the front end guesses without documentation.
This same case returns in the lesson, the diagram, the script and the 48h challenge — and continues in 4.2, 4.3 and 4.4.
1. REST principles
REST is an API architecture style: you expose resources (users, products) identified by clear plural URLs (/users, /produits), and you act on them with HTTP methods according to their semantics: GET /users (list), GET /users/:id (read one), POST /users (create), PUT/PATCH /users/:id (update), DELETE /users/:id (delete). Like Salim, never put a verb in the URL: not /getUsers, but GET /users.
The API returns data as JSON with the right status codes: 200 success, 201 created (after POST), 204 no content (after DELETE), 400 invalid request, 404 not found, 500 server error. These consistent conventions make the API predictable: the front end guesses the endpoints instead of memorising them one by one.
2. Structuring in layers (MVC)
An API that grows without structure becomes a spaghetti dish. The classic pattern is MVC: models (data structure and access), controllers (processing logic for each request), routes (URL and method definitions, which delegate to controllers). Typical folders: routes/, controllers/, models/, plus middlewares/, services/, config/.
We use the Express Router (express.Router()) to group routes by resource in separate files (one products router, one orders router), mounted on the app. This separation makes the code understandable, maintainable, testable and scalable — and lets several people work together without stepping on each other's toes.
3. Good practices of a well-designed API
What separates a professional API from one that “works”: consistent plural URLs, the right methods for the right actions, the right status codes (never 200 for an error), clean error handling (see 4.4), pagination of lists, versioning (/api/v1/...) to evolve the API without breaking clients, and structured JSON responses. Salim applies this checklist to every endpoint before saying “it's done”.
🔎 Concrete example — 🔎 Concrete example — The REST CRUD of a resource
The correspondence table Salim displays above his screen:
GET /api/v1/produits → list (200)
GET /api/v1/produits/:id → read one (200, 404 if missing)
POST /api/v1/produits → create (201, 400 if invalid)
PUT /api/v1/produits/:id → update (200, 404 if missing)
DELETE /api/v1/produits/:id → delete (204)
Reading: five lines, five method + URL pairs, five expected codes.
Lesson: the action is carried by the HTTP method, never by the URL.
Section vocabulary
- REST API
- Web API architecture style: exposing resources (clear URLs) manipulated by HTTP methods according to their semantics, responses in JSON with the right status codes.
Simple example: Salim's products API: GET to read, POST to create, JSON in response.
What it is not: a simple script returning HTML mixed with logic.
Reflex: for each endpoint, name the resource, the method and the expected code.
- Resource
- Entity exposed by the API (products, users, orders), identified by a clear plural URL.
Simple example: Salim's products resource lives under /api/v1/produits.
What it is not: a verbal action like /creerProduit.
Reflex: URL = plural noun, no verb.
- MVC
- Separating models (data), controllers (logic), routes (URLs) for maintainable, scalable code.
Simple example: Salim puts his URLs in routes/, his logic in controllers/.
What it is not: all the code piled into a single app.js file.
Reflex: one change touches one layer, not all three.
- express.Router()
- Express tool grouping a resource's routes in a separate file, then mounted on the application.
Simple example: Salim's products router is mounted with app.use('/api/v1/produits', routeur).
What it is not: declaring all routes in bulk in the main file.
Reflex: one routes file per resource.
- Status code
- HTTP number communicating the result: 200 success, 201 created, 204 no content, 400 invalid, 404 missing, 500 server error.
Simple example: Salim returns 201 after a successful POST, 404 for an unknown product.
What it is not: a systematic 200 even on error.
Reflex: the client must understand the result without reading the body.
In practice — In practice — Building a structured REST API
- Design a resource's endpoints following REST: GET /items (list), GET /items/:id (read), POST /items (create), PUT /items/:id (update), DELETE /items/:id (delete).
- Structure the project in layers: routes/ (URLs), controllers/ (logic), models/ (data) — like Salim for his catalogue.
- Use express.Router() to group a resource's routes in one file, mounted on the app (app.use('/api/v1/items', itemsRouter)).
- Return JSON with the right status codes (201 after creation, 404 if not found, 400 if invalid data) and plan for pagination and versioning.
Ready-to-say script — presenting your API to the front end (30 s)
“This is Salim: the products API follows REST conventions — GET to read, POST to create, PUT to update, DELETE to delete, under /api/v1/produits, responses in JSON with the right codes: 201 after creation, 404 if missing, 400 if invalid. The structure is layered into routes, controllers and models, so we can add resources without breaking everything.”
Say in code review; show the five-endpoint table.
Copy-paste template — Express CRUD router (tested code)
const express = require('express');
const routeur = express.Router();
let produits = [];
let prochainId = 1;
// GET /api/v1/produits → list (200)
routeur.get('/', (req, res) => {
res.json(produits);
});
// GET /api/v1/produits/:id → read one (200 / 404)
routeur.get('/:id', (req, res) => {
const produit = produits.find((p) => p.id === Number(req.params.id));
if (!produit) return res.status(404).json({ erreur: 'Product not found' });
res.json(produit);
});
// POST /api/v1/produits → create (201 / 400)
routeur.post('/', (req, res) => {
if (!req.body.nom) return res.status(400).json({ erreur: 'The name field is required' });
const produit = { id: prochainId++, nom: req.body.nom };
produits.push(produit);
res.status(201).json(produit);
});
// PUT /api/v1/produits/:id → update (200 / 404)
routeur.put('/:id', (req, res) => {
const produit = produits.find((p) => p.id === Number(req.params.id));
if (!produit) return res.status(404).json({ erreur: 'Product not found' });
if (req.body.nom) produit.nom = req.body.nom;
res.json(produit);
});
// DELETE /api/v1/produits/:id → delete (204 / 404)
routeur.delete('/:id', (req, res) => {
const index = produits.findIndex((p) => p.id === Number(req.params.id));
if (index === -1) return res.status(404).json({ erreur: 'Product not found' });
produits.splice(index, 1);
res.status(204).end();
});
module.exports = routeur;
// Mounting (requires express.json()):
// const express = require('express');
// const app = express();
// app.use(express.json());
// app.use('/api/v1/produits', routeur);
Throwaway prototype
A light separation is enough (routes + logic), but keep the REST conventions: they cost nothing and avoid rewriting.
Real team API
Like Salim: full layers (routes/, controllers/, models/, services/), one router per resource, code review on statuses.
API consumed by a mobile app
Take care of pagination and stable JSON responses: mobile caches and suffers from every broken contract.
API that must evolve
Version from the start (/api/v1/): v2 will come, and your v1 clients will not break.
✓ Best practices
- URL = plural resource, action = HTTP method.
- Structure in layers as soon as the API is real.
- Return the right status code every time.
✗ Common pitfalls
- Putting a verb in the URL (/getProduits).
- Returning 200 for everything, even errors.
- Piling all the logic into the route files.
Key takeaways
- REST API: expose RESOURCES (clear plural URLs) manipulated by HTTP METHODS according to their semantics, in JSON with the right STATUS CODES.
- REST CRUD: GET /produits (list), GET /produits/:id (read), POST (create, 201), PUT (update), DELETE (delete, 204).
- STRUCTURE in layers (MVC): models, controllers, routes + services/middlewares; express.Router() groups routes by resource.
- Good practices: consistent URLs, right codes, error handling, pagination, versioning (/api/v1/). Predictable and professional.
48h challenge — apply tomorrow
Build the complete CRUD router of 1 real resource (the 5 endpoints, right codes) and test each route including the 404 and the 400.
- I can name the 5 CRUD endpoints with their codes.
- I know how to separate routes, controllers and models.
- I have tested 1 complete router, failures included.
Frequently asked questions
What distinguishes a “well-designed” REST API from one that simply “works”?
Conventions and structure: consistent URLs on resources (no verbs), right HTTP methods according to their semantics, right status codes (201, 404, 400 — not 200 for everything), MVC layers instead of spaghetti, clean error handling (4.4), input validation, structured JSON responses, pagination and versioning (/api/v1/). A well-designed API is predictable (you guess its endpoints), maintainable and pleasant to consume — crucial because it is made to be used by others.
Should you really structure in MVC from the start, even for a small API?
Adapt to the project, but separate early: projects almost always grow and refactoring spaghetti is expensive. Small prototype: light separation (routes vs logic, with express.Router()). Real API: full layers from the start (routes/, controllers/, models/, services/) — understandable, maintainable, testable and collaborative. Neither over-engineering for an exercise, nor a catch-all “because it's small”.
In a REST API, what carries the action (create, delete)?