MVC Architecture
Let's organize the code with Model-View-Controller pattern adapted for APIs.
MVC for APIs
Routes → Controllers → Services → Models
- Routes: Define endpoints
- Controllers: Receive requests and send responses
- Services: Business logic
- Models: Database access (Prisma)
Controller
typescript
// src/controllers/product.controller.tsimport { Request, Response } from 'express';import { ProductService } from '../services/product.service'; const productService = new ProductService(); export class ProductController { async index(req: Request, res: Response) { const products = await productService.findAll(); return res.json(products); } async show(req: Request, res: Response) { const { id } = req.params; const product = await productService.findById(id); if (!product) { return res.status(404).json({ error: 'Product not found' }); } return res.json(product); } async store(req: Request, res: Response) { const product = await productService.create(req.body); return res.status(201).json(product); } async update(req: Request, res: Response) { const { id } = req.params; const product = await productService.update(id, req.body); return res.json(product); } async destroy(req: Request, res: Response) { const { id } = req.params; await productService.delete(id); return res.status(204).send(); }}Service
typescript
// src/services/product.service.tsimport { prisma } from '../config/database'; interface CreateProductDTO { name: string; price: number; description?: string;} export class ProductService { async findAll() { return prisma.product.findMany(); } async findById(id: string) { return prisma.product.findUnique({ where: { id } }); } async create(data: CreateProductDTO) { return prisma.product.create({ data }); } async update(id: string, data: Partial<CreateProductDTO>) { return prisma.product.update({ where: { id }, data, }); } async delete(id: string) { return prisma.product.delete({ where: { id } }); }}Routes with Controller
typescript
// src/routes/product.routes.tsimport { Router } from 'express';import { ProductController } from '../controllers/product.controller'; const router = Router();const controller = new ProductController(); router.get('/', (req, res) => controller.index(req, res));router.get('/:id', (req, res) => controller.show(req, res));router.post('/', (req, res) => controller.store(req, res));router.put('/:id', (req, res) => controller.update(req, res));router.delete('/:id', (req, res) => controller.destroy(req, res)); export default router;AsyncHandler Helper
typescript
// src/utils/async-handler.tsimport { Request, Response, NextFunction } from 'express'; type AsyncFunction = ( req: Request, res: Response, next: NextFunction) => Promise<any>; export const asyncHandler = (fn: AsyncFunction) => { return (req: Request, res: Response, next: NextFunction) => { Promise.resolve(fn(req, res, next)).catch(next); };}; // Usagerouter.get('/', asyncHandler(controller.index));Summary
- ✅ Controller handles HTTP
- ✅ Service contains business logic
- ✅ Clear separation of responsibilities
- ✅ Easier testing and maintenance
Next lesson: Data Validation with Zod! 🚀