nestjs-prisma-querybuilder
v1.6.4
Published
Prisma QueryBuilder with Nestjs, removing the unused data request and allowing the frontend to choose which data to get, without leaving the REST standard.
Downloads
1,353
Maintainers
Readme
Nestjs/prisma-querybuilder
Documentação / Documentation
English
How to install it?
npm i nestjs-prisma-querybuilder
If there is any CORS configuration in your project, add the properties count and page into your exposedHeaders;
In your app.module include
Querybuilder
to providersPrismaService
is your serive, to check how know create it read the documentation @nestjs/prisma;// app.module import { Querybuilder } from 'nestjs-prisma-querybuilder'; providers: [PrismaService, QuerybuilderService, Querybuilder],
QuerybuilderService
is your service and you will use it on your methods;import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; import { Prisma } from '@prisma/client'; import { Querybuilder, QueryResponse } from 'nestjs-prisma-querybuilder'; import { Request } from 'express'; import { PrismaService } from 'src/prisma.service'; @Injectable() export class QuerybuilderService { constructor(@Inject(REQUEST) private readonly request: Request, private readonly querybuilder: Querybuilder, private readonly prisma: PrismaService) {} /** * * @param model model name on schema.prisma; * @param primaryKey primaryKey name for this model on prisma.schema; * @param where object to 'where' using the prisma rules; * @param mergeWhere define if the previous where will be merged with the query where or replace that; * @param justPaginate remove any 'select' and 'include' * @param setHeaders define if will set response headers 'count' and 'page' * @param depth limit the the depth to filter/populate. default is '_5_' * @param forbiddenFields fields that will be removed from any select/filter/populate/sort * */ async query({ model, depth, where, mergeWhere, justPaginate, forbiddenFields, primaryKey = 'id', setHeaders = true }: { model: Prisma.ModelName; where?: any; depth?: number; primaryKey?: string; mergeWhere?: boolean; setHeaders?: boolean; justPaginate?: boolean; forbiddenFields?: string[]; }): Promise<Partial<QueryResponse>> { return this.querybuilder .query(primaryKey, depth, setHeaders, forbiddenFields) .then(async (query) => { if (where) query.where = mergeWhere ? { ...query.where, ...where } : where; if (setHeaders) { const count = await this.prisma[model].count({ where: query.where }); this.request.res.setHeader('count', count); } if (justPaginate) { delete query.include; delete query.select; } return { ...query }; }) .catch((err) => { if (err.response?.message) throw new BadRequestException(err.response?.message); throw new BadRequestException('Internal error processing your query string, check your parameters'); }); } }
How to use it?
You can use this frontend interface to make your queries easier -- Nestjs prisma querybuilder interface
Append your QuebruilderService in any service:
// service constructor(private readonly prisma: PrismaService, private readonly qb: QuerybuilderService) {}
Config your method:
- The
query
method will be mount the query with your @Query() fromREQUEST
, but you don't need to send him as a parameter; - The
query
will be append to theResponse.headers
withcount
property with total of objects found (include paginate) - The
query
will be receive one string with your model name, this will be used to make the count;
async UserExemple() { const query = await this.qb.query('User'); return this.prisma.user.findMany(query); }
- The
Available parameters:
models for exemple:
model User { id Int @id @default(autoincrement()) email String @unique name String? posts Post[] @@map("users") } model Post { id Int @id @default(autoincrement()) title String published Boolean? @default(false) author User? @relation(fields: [authorId], references: [id]) authorId Int? content Content[] @@map("posts") } model Content { id Int @id @default(autoincrement()) text String post Post @relation(fields: [postId], references: [id]) postId Int @@map("contents") }
Page and Limit
- By default the paginate always enable and if consumer don't send
page
andlimit
on query, will return page 1 and 10 items; - on
Response.headers
will have the propertycount
andpage
with total of items and page number; http://localhost:3000/posts?page=2&limit=10
- By default the paginate always enable and if consumer don't send
Sort
- To use
sort
needed two propertiescriteria
andfield
; criteria
is a enum withasc
anddesc
;field
is the field that sort will be applied;http://localhost:3000/posts?sort[criteria]=asc&sort[field]=title
- To use
Distinct
- All the properties will be separeted by blank space, comma or semicolon;
- To use
distinct
is needed only a string; http://localhost:3000/posts?distinct=title published
Select
All the properties will be separeted by blank space, comma or semicolon;
By default if you don't send any
select
the find just will return theid
property;If it is necessary to take the whole object it is possible to use
select=all
;Exception: If you select a relationship field will be return all the object, to select a field in one relation you can use
populate
and to find just himid
is possible to useauthorId
field;http://localhost:3000/posts?select=id title,published;authorId
To exclude fields from the return, you can use a dto on prisma response before return to the user OR use the parameter 'forbiddenFields' into query method;
- Exemple a user password or token informations;
- When using forbiddenFields select 'all' will be ignored;
Populate
- Populate is an array and that allows you to select in the fields of relationships, him need two parameters
path
andselect
; path
is the relationship reference (ex: author);select
are the fields that will be returned;select=all
is not supported by populate
primaryKey
is the reference to primary key of the relationship (optional) (default: 'id');- The populate index is needed to link the properties
path
andselect
; http://localhost:3000/posts?populate[0][path]=author&populate[0][select]=name email
- Populate is an array and that allows you to select in the fields of relationships, him need two parameters
Filter
- Can be used to filter the query with your requeriments
path
is a reference from the property that will applied the filter;value
is the value that will be filtered;filterGroup
can be used to make where with operatorsand
,or
andnot
or no operator (optional);- accepted types:
['and', 'or, 'not’]
- accepted types:
operator
can be used to personalize your filter (optional);- accepted types:
['contains', 'endsWith', 'startsWith', 'equals', 'gt', 'gte', 'in', 'lt', 'lte', 'not', 'notIn', 'hasEvery', 'hasSome', 'has', 'isEmpty']
hasEvery, hasSome and notIn
are a unique string and values are separeted by blank space?filter[0][path]=name&filter[0][operator]=hasSome&filter[0][value]=foo bar ula
- accepted types:
insensitive
can be used to filter (optional);- accepted types:
['true', 'false'] - default: 'false'
- (check prisma rules for more details - Prisma: Database collation and case sensitivity)
- accepted types:
type
needs to be used if value don't is a string;- accepted types:
['string', 'boolean', 'number', 'date' , 'object'] - default: 'string'
- 'object' accepted values: ['null', 'undefined']
- accepted types:
- filter is an array and that allows you to append some filters to the same query;
http://localhost:3000/posts?filter[0][path]=title&filter[0][value]=querybuilder&filter[1][path]=published&filter[1][value]=false
http://localhost:3000/posts?filter[1][path]=published&filter[1][value]=false&filter[1][type]=boolean
http://localhost:3000/posts?filter[0][path]=title&filter[0][value]=querybuilder&filter[0][filterGroup]=and&filter[1][path]=published&filter[1][value]=falsefilter[1][filterGroup]=and
Português
Como instalar?
npm i nestjs-prisma-querybuilder
No seu app.module inclua o
Querybuilder
aos providers:PrismaService
é o seu service, para ver como criar ele leia a documentação @nestjs/prisma;// app.module import { Querybuilder } from 'nestjs-prisma-querybuilder'; providers: [PrismaService, QuerybuilderService, Querybuilder],
QuerybuilderService
vai ser o service que será usado nos seus métodos;import { BadRequestException, Inject, Injectable } from '@nestjs/common'; import { REQUEST } from '@nestjs/core'; import { Prisma } from '@prisma/client'; import { Querybuilder, QueryResponse } from 'nestjs-prisma-querybuilder'; import { Request } from 'express'; import { PrismaService } from 'src/prisma.service'; @Injectable() export class QuerybuilderService { constructor(@Inject(REQUEST) private readonly request: Request, private readonly querybuilder: Querybuilder, private readonly prisma: PrismaService) {} /** * * @param model nome do model no schema.prisma; * @param primaryKey nome da chave primaria deste model no prisma.schema; * @param where objeto para where de acordo com as regras do prisma; * @param mergeWhere define se o where informado no parâmetro anterior será unido ou substituirá um possivel where vindo da query; * @param justPaginate remove qualquer 'select' e 'populate' da query; * @param setHeaders define se será adicionado os headers 'count' e 'page' na resposta; * @param depth limita o numero de 'niveis' que a query vai lhe permitir fazer (filter/populate). default is '_5_' * @param forbiddenFields campos que serão removidos de qualquer select/filter/populate/sort */ async query({ model, depth, where, mergeWhere, justPaginate, forbiddenFields, primaryKey = 'id', setHeaders = true }: { model: Prisma.ModelName; where?: any; depth?: number; primaryKey?: string; mergeWhere?: boolean; setHeaders?: boolean; justPaginate?: boolean; forbiddenFields?: string[]; }): Promise<Partial<QueryResponse>> { return this.querybuilder .query(primaryKey, depth, setHeaders, forbiddenFields) .then(async (query) => { if (where) query.where = mergeWhere ? { ...query.where, ...where } : where; if (setHeaders) { const count = await this.prisma[model].count({ where: query.where }); this.request.res.setHeader('count', count); } if (onlyPaginate) { delete query.include; delete query.select; } return { ...query }; }) .catch((err) => { if (err.response?.message) throw new BadRequestException(err.response?.message); throw new BadRequestException('Internal error processing your query string, check your parameters'); }); } }
Optional: Você pode adicionar uma validação adicional para o parametro
model
, mas essa validação vai variar de acordo com o seu database;Exemplo com
SQLite
if (!this.tables?.length) this.tables = await this.prisma.$queryRaw`SELECT name FROM sqlite_schema WHERE type ='table' AND name NOT LIKE 'sqlite_%';`; if (!this.tables.find((v) => v.name === model)) throw new BadRequestException('Invalid model');
Como usar?
Você pode usar essa interface para tornar suas queries mais fácies no frontend -- Nestjs prisma querybuilder interface
Adicione o Querybuilder no seu service:
// service constructor(private readonly prisma: PrismaService, private readonly qb: QuerybuilderService) {}
Configurando seu método:
- o método
query
vai montar a query baseada no @Query(), mas o mesmo é pego direto doREQUEST
, não sendo necessário passar como parâmetro; - o método
query
já vai adicionar noResponse.headers
a propriedadecount
que vai ter o total de objetos encontrados (usado para paginação); - o método
query
recebe uma string com o nome referente ao model, isso vai ser usado para fazer o count;
async UserExemple() { const query = await this.qb.query('User'); return this.prisma.user.findMany(query); }
- o método
Parametros disponiveis:
models de exemplo:
model User { id Int @id @default(autoincrement()) email String @unique name String? posts Post[] @@map("users") } model Post { id Int @id @default(autoincrement()) title String published Boolean? @default(false) author User? @relation(fields: [authorId], references: [id]) authorId Int? content Content[] @@map("posts") } model Content { id Int @id @default(autoincrement()) text String post Post @relation(fields: [postId], references: [id]) postId Int @@map("contents") }
Page e Limit
- Por padrão a páginação está sempre habilitada e se não enviado
page
elimit
na query, vai ser retornado página 1 com 10 itens; - Nos headers da response haverá a propriedade
count
com o total de itens a serem paginados; http://localhost:3000/posts?page=2&limit=10
- Por padrão a páginação está sempre habilitada e se não enviado
Sort
- Para montar o sort são necessário enviar duas propriedades
field
ecriteria
; - criteria é um enum com [‘asc’, ‘desc’];
- field é o campo pelo qual a ordenação vai ser aplicada;
http://localhost:3000/posts?sort[criteria]=asc&sort[field]=title
- Para montar o sort são necessário enviar duas propriedades
Distinct
- Todas as propriedades devem ser separadas por espaço em branco, virgula ou ponto e virgula;
- Para montar o distinct é necessário enviar apenas os valores;
http://localhost:3000/posts?distinct=title published
Select
Todas as propriedades devem ser separadas por espaço em branco, virgula ou ponto e virgula;
Por padrão se não for enviado nenhum select qualquer busca só irá retornar a propriedade
id
Se for necessário pegar todo o objeto é possível usar
select=all
,Exceção: ao dar select em um relacionamento será retornado todo o objeto do relacionamento, para usar o select em um relacionamento use o
populate
, para buscar somente oid
de um relacionamento é possível usar a colunaauthorId
http://localhost:3000/posts?select=id title,published;authorId
Para excluir campos no retorno, você pode utilizar um DTO na resposta do prisma antes de devolve-lá ao usuário OU usar o parametro 'forbiddenFields' no método query ;
- Exemplo uma senha de usuário ou informações de tokens;
- Ao usar forbiddenFields select 'all' será ignorado;
Populate
- Populate é um array que permite dar select nos campos dos relacionamentos, é composto por 2 parametros, path e select;
path
é a referencia para qual relacionamento será populado;select
são os campos que irão serem retornados;select=all
não é suportado no populate
primaryKey
nome da chave primaria do relacionamento (opcional) (default: 'id');- Podem ser feitos todos os populates necessários usando o índice do array para ligar o path ao select;
http://localhost:3000/posts?populate[0][path]=author&populate[0][select]=name email
Filter
- Pode ser usado para filtrar a consulta com os parâmetros desejados;
path
é a referencia para qual propriedade irá aplicar o filtro;value
é o valor pelo qual vai ser filtrado;filterGroup
Pode ser usado para montar o where usando os operadores [‘AND’, ‘OR’, ‘NOT’] ou nenhum operador (opcional);- opções:
['and', 'or, 'not’]
- opções:
operator
pode ser usado para personalizar a consulta (opcional);- recebe os tipos
['contains', 'endsWith', 'startsWith', 'equals', 'gt', 'gte', 'in', 'lt', 'lte', 'not', 'notIn', 'hasEvery', 'hasSome', 'has', 'isEmpty']
hasEvery, hasSome e notIn
recebe uma unica string separando os valores por um espaço em branco?filter[0][path]=name&filter[0][operator]=hasSome&filter[0][value]=foo bar ula
- recebe os tipos
insensitive
pode ser usado para personalizar a consulta (opcional);- recebe os tipos:
['true', 'false'] - default: 'false'
- (confira as regras do prisma para mais informações - Prisma: Database collation and case sensitivity)
- recebe os tipos:
type
é usado caso o valor do filter NÃO seja do tipo 'string'- recebe os tipos:
['string', 'boolean', 'number', 'date' , 'object'] - default: 'string'
- 'object' recebe os valores: ['null', 'undefined']
- recebe os tipos:
- filter é um array, podendo ser adicionados vários filtros de acordo com a necessidade da consulta;
- consulta simples
http://localhost:3000/posts?filter[0][path]=title&filter[0][value]=querybuilder&filter[1][path]=published&filter[1][value]=false
http://localhost:3000/posts?filter[1][path]=published&filter[1][value]=false&filter[1][type]=boolean
http://localhost:3000/posts?filter[0][path]=title&filter[0][value]=querybuilder&filter[0][filterGroup]=and&filter[1][path]=published&filter[1][value]=falsefilter[1][filterGroup]=and
- Nestjs/Prisma Querybuilder is MIT licensed.