Express Cheatsheet
Building a REST API
Use this Express reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.
REST Conventions
Standard resource-oriented endpoint shapes — predictable methods, paths, and status codes.
| Action | Method | Path | Status |
|---|---|---|---|
| List all | GET | /resources | 200 |
| Get one | GET | /resources/:id | 200 / 404 |
| Create | POST | /resources | 201 + Location |
| Replace | PUT | /resources/:id | 200 / 204 |
| Partial update | PATCH | /resources/:id | 200 / 204 |
| Delete | DELETE | /resources/:id | 204 / 200 |
Minimal REST Router
// routes/users.js const router = require('express').Router(); const db = require('../db'); // GET /users router.get('/', async (req, res, next) => { try { const users = await db.users.findAll(); res.json(users); } catch (err) { next(err); } }); // GET /users/:id router.get('/:id', async (req, res, next) => { try { const user = await db.users.findById(req.params.id); if (!user) return res.status(404).json({ error: 'User not found' }); res.json(user); } catch (err) { next(err); } }); // POST /users router.post('/', async (req, res, next) => { try { const user = await db.users.create(req.body); res.status(201) .location(`/users/${user.id}`) .json(user); } catch (err) { next(err); } }); // PUT /users/:id — full replacement router.put('/:id', async (req, res, next) => { try { const user = await db.users.replace(req.params.id, req.body); if (!user) return res.status(404).json({ error: 'User not found' }); res.json(user); } catch (err) { next(err); } }); // PATCH /users/:id — partial update router.patch('/:id', async (req, res, next) => { try { const user = await db.users.update(req.params.id, req.body); if (!user) return res.status(404).json({ error: 'User not found' }); res.json(user); } catch (err) { next(err); } }); // DELETE /users/:id router.delete('/:id', async (req, res, next) => { try { await db.users.delete(req.params.id); res.sendStatus(204); } catch (err) { next(err); } }); module.exports = router;
Mount it:
app.use('/api/v1/users', require('./routes/users'));
Pagination
router.get('/', async (req, res, next) => { try { const page = Math.max(1, parseInt(req.query.page, 10) || 1); const limit = Math.min(100, parseInt(req.query.limit, 10) || 20); const offset = (page - 1) * limit; const [rows, total] = await Promise.all([ db.items.findAll({ limit, offset }), db.items.count(), ]); const totalPages = Math.ceil(total / limit); res.json({ data: rows, meta: { page, limit, total, totalPages }, links: { self: `/items?page=${page}&limit=${limit}`, next: page < totalPages ? `/items?page=${page + 1}&limit=${limit}` : null, prev: page > 1 ? `/items?page=${page - 1}&limit=${limit}` : null, first: `/items?page=1&limit=${limit}`, last: `/items?page=${totalPages}&limit=${limit}`, }, }); } catch (err) { next(err); } });
Filtering and Sorting
router.get('/', async (req, res, next) => { const SORTABLE = ['name', 'created_at', 'price']; const ORDERABLE = ['asc', 'desc']; const sort = SORTABLE.includes(req.query.sort) ? req.query.sort : 'created_at'; const order = ORDERABLE.includes(req.query.order) ? req.query.order : 'desc'; const search = req.query.search?.trim().slice(0, 100) || ''; try { const items = await db.items.search({ sort, order, search }); res.json(items); } catch (err) { next(err); } });
Consistent Error Envelope
// Centralized error response helper const fail = (res, status, code, message, details = undefined) => res.status(status).json({ error: { code, message, ...(details && { details }) } }); // Usage if (!user) return fail(res, 404, 'USER_NOT_FOUND', 'No user with that ID'); if (!valid) return fail(res, 400, 'VALIDATION_ERROR', 'Invalid input', errors);
Input Validation with express-validator
const { body, param, validationResult } = require('express-validator'); const createUserRules = [ body('email').isEmail().normalizeEmail(), body('name').trim().isLength({ min: 1, max: 100 }), body('age').optional().isInt({ min: 0, max: 150 }).toInt(), ]; const validate = (req, res, next) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } next(); }; router.post('/', createUserRules, validate, async (req, res, next) => { // req.body is clean and validated });
Versioning
// URI versioning (most common) app.use('/api/v1', require('./routes/v1')); app.use('/api/v2', require('./routes/v2')); // Header versioning app.use('/api', (req, res, next) => { const version = req.get('API-Version') || '1'; req.apiVersion = version; next(); });
Nested Resources
// GET /users/:userId/posts // POST /users/:userId/posts const postsRouter = require('express').Router({ mergeParams: true }); postsRouter.get('/', async (req, res, next) => { // req.params.userId available because mergeParams: true const posts = await db.posts.findByUser(req.params.userId); res.json(posts); }); postsRouter.post('/', async (req, res, next) => { const post = await db.posts.create({ ...req.body, userId: req.params.userId }); res.status(201).json(post); }); // Mount under users router usersRouter.use('/:userId/posts', postsRouter);
HTTP Status Code Reference
| Status | Meaning | When to Use |
|---|---|---|
| 200 | OK | Successful GET, PUT, PATCH |
| 201 | Created | Successful POST |
| 204 | No Content | Successful DELETE; PUT/PATCH with no body |
| 400 | Bad Request | Invalid input, failed validation |
| 401 | Unauthorized | Missing or invalid credentials |
| 403 | Forbidden | Authenticated but not authorized |
| 404 | Not Found | Resource doesn't exist |
| 405 | Method Not Allowed | HTTP method not supported |
| 409 | Conflict | Duplicate, version conflict |
| 410 | Gone | Resource permanently deleted |
| 413 | Payload Too Large | Body exceeds size limit |
| 422 | Unprocessable Entity | Valid JSON but semantic errors |
| 429 | Too Many Requests | Rate limited |
| 500 | Internal Server Error | Unexpected server failure |
| 503 | Service Unavailable | Server overloaded or down for maintenance |
HATEOAS Links
// Embed navigational links in responses res.json({ id: user.id, name: user.name, _links: { self: { href: `/users/${user.id}`, method: 'GET' }, update: { href: `/users/${user.id}`, method: 'PATCH' }, delete: { href: `/users/${user.id}`, method: 'DELETE' }, posts: { href: `/users/${user.id}/posts`, method: 'GET' }, }, });
Content Negotiation
router.get('/:id', async (req, res, next) => { try { const resource = await db.items.findById(req.params.id); if (!resource) return res.sendStatus(404); res.format({ 'application/json': () => res.json(resource), 'text/html': () => res.render('item', { resource }), 'text/csv': () => res.send(toCsv(resource)), default: () => res.status(406).send('Not Acceptable'), }); } catch (err) { next(err); } });