REST Conventions
// URLs = nouns, HTTP methods = verbs:
// GET /api/users → list all
// GET /api/users/:id → get one
// POST /api/users → create
// PUT /api/users/:id → replace
// PATCH /api/users/:id → partial update
// DELETE /api/users/:id → delete
// Status codes that matter:
// 200 OK, 201 Created, 204 No Content
// 400 Bad Request, 401 Unauthorized, 403 Forbidden
// 404 Not Found, 409 Conflict, 422 Validation Failed
// 500 Internal Server Error
Complete CRUD API
const express = require("express");
const app = express();
app.use(express.json());
let users = [{ id: 1, name: "Alice", email: "alice@ex.com" }];
let nextId = 2;
app.get("/api/users", (req, res) => {
const { search, page = 1, limit = 10 } = req.query;
let result = users;
if (search) result = result.filter(u =>
u.name.toLowerCase().includes(search.toLowerCase()));
const total = result.length;
result = result.slice((page-1)*limit, page*limit);
res.json({ data: result, total, page: +page });
});
app.get("/api/users/:id", (req, res) => {
const user = users.find(u => u.id === +req.params.id);
if (!user) return res.status(404).json({ error: "Not found" });
res.json({ data: user });
});
app.post("/api/users", (req, res) => {
const { name, email } = req.body;
if (!name || !email)
return res.status(400).json({ error: "name and email required" });
if (users.find(u => u.email === email))
return res.status(409).json({ error: "Email exists" });
const user = { id: nextId++, name, email };
users.push(user);
res.status(201).json({ data: user });
});
app.patch("/api/users/:id", (req, res) => {
const i = users.findIndex(u => u.id === +req.params.id);
if (i === -1) return res.status(404).json({ error: "Not found" });
users[i] = { ...users[i], ...req.body, id: users[i].id };
res.json({ data: users[i] });
});
app.delete("/api/users/:id", (req, res) => {
const i = users.findIndex(u => u.id === +req.params.id);
if (i === -1) return res.status(404).json({ error: "Not found" });
users.splice(i, 1);
res.status(204).send();
});
app.listen(3000);
⚡ Key Takeaways
- REST: consistent URL patterns (nouns) + HTTP verbs
- 201 Created for POST, 204 No Content for DELETE
- Validate input — return 400 for bad data, 409 for conflicts
- Consistent response envelope: { data } or { error }
- Always validate uniqueness before creating (email, username)
- For production, replace arrays with a real database
🎯 Practice Exercises
EXERCISE 1
Add pagination to the GET /users route: return total, page, totalPages in addition to data. Test with various page and limit values.
EXERCISE 2 — CHALLENGE
Build a complete blog API: Posts (title, body, authorId) and Comments (postId, text, author). Nested route: GET /api/posts/:id/comments.