<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Cristian Israel</title>
    <description>The latest articles on DEV Community by Cristian Israel (@cristian-israel).</description>
    <link>https://dev.to/cristian-israel</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3855678%2F0b7db0e5-ed8b-4a02-a4a5-e3c7547a90cf.png</url>
      <title>DEV Community: Cristian Israel</title>
      <link>https://dev.to/cristian-israel</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/cristian-israel"/>
    <language>en</language>
    <item>
      <title>Como vocês estruturam paginação + filtros + ordenação em APIs REST? (NestJS / TypeORM)</title>
      <dc:creator>Cristian Israel</dc:creator>
      <pubDate>Wed, 01 Apr 2026 13:15:13 +0000</pubDate>
      <link>https://dev.to/cristian-israel/como-voces-estruturam-paginacao-filtros-ordenacao-em-apis-rest-nestjs-typeorm-4lpd</link>
      <guid>https://dev.to/cristian-israel/como-voces-estruturam-paginacao-filtros-ordenacao-em-apis-rest-nestjs-typeorm-4lpd</guid>
      <description>&lt;p&gt;Estou evoluindo a arquitetura das minhas APIs e comecei a pensar mais seriamente sobre como estruturar &lt;strong&gt;paginação, filtros e ordenação&lt;/strong&gt; 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.&lt;/p&gt;

&lt;p&gt;O cenário que estou tentando resolver é este:&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;filtros (ex: &lt;code&gt;active=true&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;ordenação (ex: &lt;code&gt;createdAt DESC&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;busca por texto&lt;/li&gt;
&lt;li&gt;metadata de paginação&lt;/li&gt;
&lt;li&gt;possivelmente relations&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;O problema conceitual que percebi é:&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Então entendi que o fluxo correto deveria sempre ser:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Database → WHERE → ORDER BY → LIMIT → OFFSET → Response&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;E nunca:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Database → LIMIT → frontend filter → frontend sort&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Ou seja, filtro e ordenação sempre devem acontecer &lt;strong&gt;antes&lt;/strong&gt; da paginação.&lt;/p&gt;




&lt;h2&gt;
  
  
  Estrutura de query que estou pensando em usar
&lt;/h2&gt;

&lt;p&gt;Algo nesse formato:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;GET /users?page=1&amp;amp;limit=10&amp;amp;active=true&amp;amp;sortBy=createdAt&amp;amp;order=DESC&amp;amp;search=cris
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Com um DTO base tipo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QueryDto&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nl"&gt;page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;sortBy&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;order&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ASC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DESC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DESC&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="nl"&gt;search&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E um DTO específico:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UserQueryDto&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nc"&gt;QueryDto&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;

  &lt;span class="nl"&gt;active&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Implementação que estou considerando no service (QueryBuilder)
&lt;/h2&gt;

&lt;p&gt;Algo nessa linha:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;qb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;createQueryBuilder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;

  &lt;span class="nx"&gt;qb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;andWhere&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user.active = :active&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;active&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;

  &lt;span class="nx"&gt;qb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;andWhere&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user.name ILIKE :search&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;search&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;`%&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;%`&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;qb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`user.&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;sortBy&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;order&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;qb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;take&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;qb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;skip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;qb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getManyAndCount&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Response pattern que estou pensando em padronizar
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[],&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="nl"&gt;"meta"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"total"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;48&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"perPage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"currentPage"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"totalPages"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"currentPageCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"hasNext"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nl"&gt;"hasPrevious"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Também pensei em talvez incluir &lt;code&gt;links&lt;/code&gt; (first, last, next, previous), mas ainda estou avaliando se vale a pena.&lt;/p&gt;




&lt;h2&gt;
  
  
  Ideia de arquitetura que estou considerando
&lt;/h2&gt;

&lt;p&gt;Criar algo reutilizável como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PaginationHelper&lt;/li&gt;
&lt;li&gt;BaseCrudService&lt;/li&gt;
&lt;li&gt;BaseQueryDto&lt;/li&gt;
&lt;li&gt;Filter DTO por entidade&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Para evitar duplicar lógica em todos os services.&lt;/p&gt;

&lt;p&gt;Mas não sei até que ponto isso ajuda ou vira overengineering cedo demais.&lt;/p&gt;




&lt;h2&gt;
  
  
  Dúvidas que queria ouvir opiniões de quem já trabalhou com APIs maiores:
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1)&lt;/strong&gt; Vocês preferem usar &lt;code&gt;repository.find()&lt;/code&gt; enquanto dá ou já partem direto para QueryBuilder quando existe filtro dinâmico?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2)&lt;/strong&gt; Vocês deixam &lt;code&gt;sortBy&lt;/code&gt; livre ou fazem whitelist de campos permitidos?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3)&lt;/strong&gt; Preferem separar:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PaginationDto&lt;/li&gt;
&lt;li&gt;FilterDto&lt;/li&gt;
&lt;li&gt;SortDto&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Ou usar um único QueryDTO?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4)&lt;/strong&gt; Vale a pena criar um BaseCrudService genérico ou isso costuma virar complexidade desnecessária?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5)&lt;/strong&gt; Como vocês estruturam filtros mais complexos como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;date ranges&lt;/li&gt;
&lt;li&gt;LIKE search&lt;/li&gt;
&lt;li&gt;múltiplos filtros combinados&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;6)&lt;/strong&gt; Vocês deixam o frontend enviar qualquer filtro ou criam um mapper controlando o que pode ser filtrado?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7)&lt;/strong&gt; Em projetos maiores vocês costumam usar algo como:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Specification Pattern&lt;/li&gt;
&lt;li&gt;Query Objects&lt;/li&gt;
&lt;li&gt;Custom repositories&lt;/li&gt;
&lt;li&gt;CQRS&lt;/li&gt;
&lt;li&gt;outro padrão?&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Objetivo
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Queria muito entender:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;como vocês fazem em produção&lt;/li&gt;
&lt;li&gt;o que vale a pena já fazer cedo&lt;/li&gt;
&lt;li&gt;o que vocês evitariam se começassem de novo&lt;/li&gt;
&lt;li&gt;erros comuns nesse tipo de arquitetura&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Se alguém puder compartilhar experiências reais ou padrões que funcionaram bem seria muito útil.&lt;/p&gt;




&lt;h2&gt;
  
  
  Stack atual:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;NestJS&lt;/li&gt;
&lt;li&gt;TypeORM&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;Axios no frontend&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Qualquer insight ou experiência prática já ajuda bastante.&lt;/p&gt;

</description>
      <category>api</category>
      <category>architecture</category>
      <category>backend</category>
      <category>discuss</category>
    </item>
  </channel>
</rss>
