Skip to contentPedro Farbo
Lesson 10 / 2555 min

Complete CRUD

Complete CRUD

Let's implement a complete CRUD with pagination, filters and sorting.

Product Service

typescript
// src/services/product.service.tsimport { prisma } from '../config/database';import { AppError } from '../errors/AppError';import { Prisma } from '@prisma/client'; interface FindAllParams {  page?: number;  limit?: number;  search?: string;  categoryId?: string;  sortBy?: string;  order?: 'asc' | 'desc';} export class ProductService {  async findAll(params: FindAllParams) {    const {      page = 1,      limit = 20,      search,      categoryId,      sortBy = 'createdAt',      order = 'desc'    } = params;     const where: Prisma.ProductWhereInput = {};     if (search) {      where.OR = [        { name: { contains: search, mode: 'insensitive' } },        { description: { contains: search, mode: 'insensitive' } },      ];    }     if (categoryId) {      where.categoryId = categoryId;    }     const skip = (page - 1) * limit;     const [products, total] = await Promise.all([      prisma.product.findMany({        where,        skip,        take: limit,        orderBy: { [sortBy]: order },        include: { category: true },      }),      prisma.product.count({ where }),    ]);     return {      data: products,      pagination: {        page,        limit,        total,        totalPages: Math.ceil(total / limit),      },    };  }   async findById(id: string) {    const product = await prisma.product.findUnique({      where: { id },      include: { category: true },    });     if (!product) {      throw new AppError('Product not found', 404);    }     return product;  }   async create(data: CreateProductDTO) {    const slug = data.name.toLowerCase().replace(/\s+/g, '-');     const exists = await prisma.product.findUnique({ where: { slug } });    if (exists) {      throw new AppError('Product already exists', 409);    }     return prisma.product.create({      data: { ...data, slug },      include: { category: true },    });  }   async update(id: string, data: UpdateProductDTO) {    await this.findById(id);     return prisma.product.update({      where: { id },      data,      include: { category: true },    });  }   async delete(id: string) {    await this.findById(id);    return prisma.product.delete({ where: { id } });  }}

Controller

typescript
// src/controllers/product.controller.tsexport class ProductController {  async index(req: Request, res: Response) {    const { page, limit, search, categoryId, sortBy, order } = req.query;     const result = await productService.findAll({      page: Number(page) || 1,      limit: Number(limit) || 20,      search: search as string,      categoryId: categoryId as string,      sortBy: sortBy as string,      order: order as 'asc' | 'desc',    });     return res.json(result);  }   async show(req: Request, res: Response) {    const product = await productService.findById(req.params.id);    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 product = await productService.update(req.params.id, req.body);    return res.json(product);  }   async destroy(req: Request, res: Response) {    await productService.delete(req.params.id);    return res.status(204).send();  }}

Testing Endpoints

bash
# List with paginationGET /api/products?page=1&limit=10 # SearchGET /api/products?search=iphone # Filter by categoryGET /api/products?categoryId=abc-123 # SortGET /api/products?sortBy=price&order=asc

Summary

  • ✅ CRUD with Prisma
  • ✅ Pagination
  • ✅ Text search
  • ✅ Filters and sorting

Next lesson: Database Relationships! 🚀

Enjoyed the content? Your contribution helps keep everything online and free!

PIX:0737160d-e98f-4a65-8392-5dba70e7ff3e