Building REST APIs with Node.js
Building REST APIs with Node.js
Section titled “Building REST APIs with Node.js”Node.js with Express is one of the most popular stacks for REST APIs. This page covers the patterns needed for production-grade APIs.
Basic Express API
Section titled “Basic Express API”npm install expressnpm install -D @types/express typescript ts-nodeimport express, { Request, Response } from 'express';
const app = express();app.use(express.json());
const users: User[] = [];
app.get('/api/users', (req: Request, res: Response) => { res.json(users);});
app.get('/api/users/:id', (req: Request, res: Response) => { const user = users.find(u => u.id === req.params.id); if (!user) return res.status(404).json({ error: 'User not found' }); res.json(user);});
app.post('/api/users', (req: Request, res: Response) => { const user = { id: crypto.randomUUID(), ...req.body }; users.push(user); res.status(201).json(user);});
app.put('/api/users/:id', (req: Request, res: Response) => { const index = users.findIndex(u => u.id === req.params.id); if (index === -1) return res.status(404).json({ error: 'User not found' }); users[index] = { ...users[index], ...req.body }; res.json(users[index]);});
app.delete('/api/users/:id', (req: Request, res: Response) => { const index = users.findIndex(u => u.id === req.params.id); if (index === -1) return res.status(404).json({ error: 'User not found' }); users.splice(index, 1); res.status(204).send();});
app.listen(3000, () => console.log('API running on port 3000'));Router Modules
Section titled “Router Modules”Split routes into separate files:
import { Router } from 'express';
const router = Router();
router.get('/', getUsers);router.get('/:id', getUserById);router.post('/', createUser);router.put('/:id', updateUser);router.delete('/:id', deleteUser);
export default router;import usersRouter from './routes/users';app.use('/api/users', usersRouter);Input Validation with Zod
Section titled “Input Validation with Zod”npm install zodimport { z } from 'zod';
const CreateUserSchema = z.object({ name: z.string().min(1).max(100), email: z.string().email(), age: z.number().int().min(0).max(150),});
function validateBody(schema: z.ZodSchema) { return (req: Request, res: Response, next: NextFunction) => { const result = schema.safeParse(req.body); if (!result.success) { return res.status(400).json({ errors: result.error.flatten() }); } req.body = result.data; next(); };}
router.post('/', validateBody(CreateUserSchema), createUser);Error Handling
Section titled “Error Handling”// Async wrapper to avoid try/catch in every routefunction asyncHandler(fn: RequestHandler): RequestHandler { return (req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);}
// Global error handler — must have 4 parametersapp.use((err: Error, req: Request, res: Response, next: NextFunction) => { console.error(err.stack); res.status(500).json({ error: err.message });});Authentication with JWT
Section titled “Authentication with JWT”npm install jsonwebtoken bcryptjsnpm install -D @types/jsonwebtoken @types/bcryptjsimport jwt from 'jsonwebtoken';
const JWT_SECRET = process.env.JWT_SECRET!;
// Generate tokenfunction generateToken(userId: string): string { return jwt.sign({ sub: userId }, JWT_SECRET, { expiresIn: '7d' });}
// Verify token middlewarefunction authenticate(req: Request, res: Response, next: NextFunction) { const token = req.headers.authorization?.replace('Bearer ', ''); if (!token) return res.status(401).json({ error: 'No token' });
try { const payload = jwt.verify(token, JWT_SECRET) as { sub: string }; req.userId = payload.sub; next(); } catch { res.status(401).json({ error: 'Invalid token' }); }}
// Protected routerouter.get('/profile', authenticate, (req, res) => { res.json({ userId: req.userId });});Response Patterns
Section titled “Response Patterns”// Consistent response shapefunction sendSuccess(res: Response, data: unknown, status = 200) { res.status(status).json({ success: true, data });}
function sendError(res: Response, message: string, status = 400) { res.status(status).json({ success: false, error: message });}
// Paginationapp.get('/api/posts', (req, res) => { const page = Number(req.query.page) || 1; const limit = Math.min(Number(req.query.limit) || 20, 100); const offset = (page - 1) * limit;
const items = posts.slice(offset, offset + limit); res.json({ data: items, pagination: { page, limit, total: posts.length }, });});HTTP Status Codes Reference
Section titled “HTTP Status Codes Reference”| Status | Meaning |
|---|---|
| 200 | OK |
| 201 | Created |
| 204 | No Content (successful delete) |
| 400 | Bad Request (validation error) |
| 401 | Unauthorized (not authenticated) |
| 403 | Forbidden (authenticated but no permission) |
| 404 | Not Found |
| 409 | Conflict (duplicate resource) |
| 422 | Unprocessable Entity |
| 500 | Internal Server Error |