nestjs-rest-querynestjs-rest-query

Introdução

O que é o nestjs-rest-query e por que utilizá-lo.

Estas páginas descrevem a v3, publicada como prerelease

A API 3.x descrita neste site está no npm como 3.0.0-alpha.0, sob a tag alpha — pnpm add nestjs-rest-query@alpha. A tag latest continua apontando para a 2.1.0, então uma instalação sem a tag entrega a API 2.x. A 3.0.0 estável depende de um gate só — a validação deste alpha por um consumidor de fora; a lista completa está em docs/v3/status.md. Se você está na 2.x hoje, leia o guia de migração antes de subir de versão — a v3 não tem modo de compatibilidade.

O nestjs-rest-query transforma parâmetros de query HTTP — filtros, ordenações, paginação, seleção de campos, carregamento de relações e busca textual — num plano de query tipado, autoriza esse plano contra uma whitelist por endpoint e deixa um adapter de ORM compilá-lo. TypeORM, Prisma e Drizzle são atendidos pelo mesmo núcleo, e devem responder à mesma requisição da mesma forma.

Fluxo principal

Rendering Mermaid diagram...

As regras compiladas do endpoint são a fonte única de duas coisas ao mesmo tempo: a autorização em runtime e os parâmetros OpenAPI que @ApiDynamicQuery(rules) gera. Elas não podem divergir, porque não são duas declarações.

O que a v3 mudou

Se você conhece a API 2.x, quatro coisas são diferentes em natureza, não em grau:

  • O schema é declarado, não adivinhado. O kind declarado do campo decide como o texto recebido é coagido, então "00430123" continua a string "00430123" em vez de virar o número 430123.
  • O adapter vem da source, não do forRoot. O pacote raiz não carrega ORM nenhum; forRoot({ adapter }) é recusado na inicialização.
  • A whitelist casa paths exatos. Autorizar a relação company não autoriza mais company.name.
  • Configuração impossível falha quando a aplicação sobe, não na primeira requisição que por acaso passe por ela.

Recursos

  • Filtros dinâmicos — 14 operadores (eq, ne, like, ilike, notLike, notIlike, gt, gte, lt, lte, in, notIn, between, isNull), autorizados por campo
  • Ordenação — múltiplas colunas, - para descendente, recusada onde a ordem não seria portável entre bancos
  • Paginação — page/perPage com total e lastPage, ou paginate=false
  • Seleção de campos — fields=, limitado por uma projeção declarada por nível
  • Carregamento de relações — includes=, cada relação com a projeção de campos dela
  • Busca textual portável — search= sobre colunas pré-normalizadas (dobradas), então a mesma requisição devolve as mesmas linhas independentemente da collation do servidor
  • Envelope de erro estável — toda recusa carrega um code legível por máquina
  • Três adapters de ORM atrás de um núcleo só, mais um corpus de paridade que compara os três

Endpoint mínimo

Duas declarações e uma chamada. Este é o apps/examples/01-starter-app, aparado:

product.query.ts
import { defineQueryRules, defineQuerySchema } from 'nestjs-rest-query';
import type { QuerySchema, SchemaRegistry } from 'nestjs-rest-query';

const productSchema: QuerySchema = defineQuerySchema({
  model: 'product',
  primaryKey: ['id'],
  fields: [
    { path: 'id', kind: 'integer', nullable: false, primaryKey: true },
    {
      path: 'name',
      kind: 'string',
      nullable: false,
      primaryKey: false,
      foldedField: 'name_folded',
    },
    {
      path: 'name_folded',
      kind: 'string',
      nullable: false,
      primaryKey: false,
      internal: true,
    },
    { path: 'price', kind: 'decimal', nullable: false, primaryKey: false },
    { path: 'createdAt', kind: 'datetime', nullable: false, primaryKey: false },
  ],
  relations: [],
});

export const PRODUCT_SCHEMAS: SchemaRegistry = new Map([
  ['product', productSchema],
]);

export const productRules = defineQueryRules(PRODUCT_SCHEMAS, 'product', {
  filters: [
    { path: 'id', operators: ['eq', 'in'] },
    { path: 'name', operators: ['eq', 'like', 'ilike'] },
    { path: 'price', operators: ['eq', 'gt', 'gte', 'lt', 'lte', 'between'] },
  ],
  sorts: ['id', 'name', 'price', 'createdAt'],
  fields: {
    root: {
      allowed: ['id', 'name', 'price', 'createdAt'],
      default: ['id', 'name', 'price', 'createdAt'],
    },
  },
  search: ['name'],
});
product.controller.ts
@Get()
@ApiDynamicQuery(productRules)
async findAll(
  @Query() query: DynamicQueryDto,
  @QueryRules() rules: CompiledQueryRules,
) {
  return this.productService.findAll(query, rules);
}
product.service.ts
import { typeormSource } from 'nestjs-rest-query/typeorm';

async findAll(
  query: DynamicQueryDto,
  rules: CompiledQueryRules,
): Promise<NormalizedQueryResult<Product>> {
  return this.queryBuilderService.execute(
    typeormSource(this.productRepository),
    query,
    rules,
  );
}

productRules é construído no carregamento do módulo, fora da classe, porque @ApiDynamicQuery é um decorator de método e roda antes de o Nest ter container ou repositório.

Comece por aqui

Editar esta página no GitHub

On this page