Error Handling
A robust error handling system is essential for production APIs.
Custom Error Class
typescript
// src/errors/AppError.tsexport class AppError extends Error { public readonly statusCode: number; public readonly isOperational: boolean; constructor(message: string, statusCode: number = 400) { super(message); this.statusCode = statusCode; this.isOperational = true; Error.captureStackTrace(this, this.constructor); }}Global Error Middleware
typescript
// src/middlewares/error.middleware.tsimport { Request, Response, NextFunction } from 'express';import { AppError } from '../errors/AppError';import { ZodError } from 'zod'; export const errorHandler = ( err: Error, req: Request, res: Response, next: NextFunction) => { // Zod validation errors if (err instanceof ZodError) { return res.status(400).json({ status: 'error', message: 'Validation error', errors: err.errors, }); } // Our custom errors if (err instanceof AppError) { return res.status(err.statusCode).json({ status: 'error', message: err.message, }); } // Log unexpected errors console.error('Unexpected error:', err); // Don't expose details in production return res.status(500).json({ status: 'error', message: process.env.NODE_ENV === 'production' ? 'Internal server error' : err.message, });};Using in Services
typescript
// src/services/product.service.tsimport { AppError } from '../errors/AppError'; export class ProductService { async findById(id: string) { const product = await prisma.product.findUnique({ where: { id } }); if (!product) { throw new AppError('Product not found', 404); } return product; } async create(data: CreateProductDTO) { const exists = await prisma.product.findFirst({ where: { name: data.name }, }); if (exists) { throw new AppError('Product with this name already exists', 409); } return prisma.product.create({ data }); }}Async Handler
typescript
// src/utils/async-handler.tsimport { Request, Response, NextFunction } from 'express'; export const asyncHandler = (fn: Function) => { return (req: Request, res: Response, next: NextFunction) => { Promise.resolve(fn(req, res, next)).catch(next); };}; // Usagerouter.get('/:id', asyncHandler(async (req, res) => { const product = await productService.findById(req.params.id); res.json(product);}));404 Handler
typescript
// After all routesapp.use((req, res) => { res.status(404).json({ status: 'error', message: `Route ${req.method} ${req.path} not found`, });}); // Error handler always at the endapp.use(errorHandler);Summary
- ✅ Custom AppError class
- ✅ Centralized error middleware
- ✅ Async error handling
- ✅ Logging and environment-aware responses
Next lesson: Introduction to Databases! 🚀