DEV Community

Cristian Israel
Cristian Israel

Posted on

Como vocês estruturam paginação + filtros + ordenação em APIs REST? (NestJS / TypeORM)

Estou evoluindo a arquitetura das minhas APIs e comecei a pensar mais seriamente sobre como estruturar paginação, filtros e ordenação de forma realmente escalável, como vejo em projetos maiores. A ideia é evitar aquele CRUD simples que depois vira um problema quando a API cresce.

O cenário que estou tentando resolver é este:

Tenho cerca de 50 registros de usuários e uso paginação com page e limit (ex: 10 por página). Porém também preciso suportar:

  • filtros (ex: active=true)
  • ordenação (ex: createdAt DESC)
  • busca por texto
  • metadata de paginação
  • possivelmente relations

O problema conceitual que percebi é:

Se o frontend filtrar ou ordenar depois da paginação, ele só trabalha com os 10 registros da página atual e não com os 50 registros totais. Isso gera resultados inconsistentes.

Então entendi que o fluxo correto deveria sempre ser:

Database → WHERE → ORDER BY → LIMIT → OFFSET → Response

E nunca:

Database → LIMIT → frontend filter → frontend sort

Ou seja, filtro e ordenação sempre devem acontecer antes da paginação.


Estrutura de query que estou pensando em usar

Algo nesse formato:

GET /users?page=1&limit=10&active=true&sortBy=createdAt&order=DESC&search=cris
Enter fullscreen mode Exit fullscreen mode

Com um DTO base tipo:

export class QueryDto {

  page:number = 1;

  limit:number = 10;

  sortBy?:string;

  order:'ASC'|'DESC' = 'DESC';

  search?:string;

}
Enter fullscreen mode Exit fullscreen mode

E um DTO específico:

export class UserQueryDto extends QueryDto{

  active?:boolean;

}
Enter fullscreen mode Exit fullscreen mode

Implementação que estou considerando no service (QueryBuilder)

Algo nessa linha:

const qb =
this.repository.createQueryBuilder('user');

if(active){

  qb.andWhere(
    'user.active = :active',
    {active}
  );

}

if(search){

  qb.andWhere(
    'user.name ILIKE :search',
    {search:`%${search}%`}
  );

}

qb.orderBy(
  `user.${sortBy}`,
  order
);

qb.take(limit);

qb.skip(offset);

const [data,total] =
await qb.getManyAndCount();
Enter fullscreen mode Exit fullscreen mode

Response pattern que estou pensando em padronizar

{
  "data": [],

  "meta": {

    "total":48,

    "perPage":10,

    "currentPage":1,

    "totalPages":5,

    "currentPageCount":10,

    "hasNext":true,

    "hasPrevious":false

  }
}
Enter fullscreen mode Exit fullscreen mode

Também pensei em talvez incluir links (first, last, next, previous), mas ainda estou avaliando se vale a pena.


Ideia de arquitetura que estou considerando

Criar algo reutilizável como:

  • PaginationHelper
  • BaseCrudService
  • BaseQueryDto
  • Filter DTO por entidade

Para evitar duplicar lógica em todos os services.

Mas não sei até que ponto isso ajuda ou vira overengineering cedo demais.


Dúvidas que queria ouvir opiniões de quem já trabalhou com APIs maiores:

1) Vocês preferem usar repository.find() enquanto dá ou já partem direto para QueryBuilder quando existe filtro dinâmico?

2) Vocês deixam sortBy livre ou fazem whitelist de campos permitidos?

3) Preferem separar:

  • PaginationDto
  • FilterDto
  • SortDto

Ou usar um único QueryDTO?

4) Vale a pena criar um BaseCrudService genérico ou isso costuma virar complexidade desnecessária?

5) Como vocês estruturam filtros mais complexos como:

  • date ranges
  • LIKE search
  • múltiplos filtros combinados

6) Vocês deixam o frontend enviar qualquer filtro ou criam um mapper controlando o que pode ser filtrado?

7) Em projetos maiores vocês costumam usar algo como:

  • Specification Pattern
  • Query Objects
  • Custom repositories
  • CQRS
  • outro padrão?

Objetivo

Estou tentando sair de CRUD básico e começar a estruturar um padrão mais próximo do que vejo em projetos mais maduros, principalmente para evitar retrabalho quando a API começar a crescer.

Queria muito entender:

  • como vocês fazem em produção
  • o que vale a pena já fazer cedo
  • o que vocês evitariam se começassem de novo
  • erros comuns nesse tipo de arquitetura

Se alguém puder compartilhar experiências reais ou padrões que funcionaram bem seria muito útil.


Stack atual:

  • NestJS
  • TypeORM
  • PostgreSQL
  • Axios no frontend

Qualquer insight ou experiência prática já ajuda bastante.

Top comments (0)