@chax-at/prisma-filter

中文文档
5.0.0发布于 2026/01/28MITTypeScriptCommonJS构建来源已验证近 30 天下载 5914

The PrismaFilter package allows the frontend to request filtered, sorted, and paginated data via query parameters. The package automatically generates Prisma findOptions from these query parameters, which can then be directly used in the backend.

原始使用文档
文档目录11 节
  1. ⚠️ WARNING for NestJS 11+
  2. Usage - Frontend
  3. Building a filter
  4. Filter types
  5. Filter value types
  6. Usage - Backend
  7. Prerequisites
  8. DirectFilterPipe
  9. AllFilterPipeUnsafe
  10. FilterPipe
  11. FilterParser

@chax-at/prisma-filter

This package provides a NestJS transformation pipe that transforms REST query parameters into findOptions for Prisma. The query parameters use the same structure as Tabulator.

⚠️ WARNING for NestJS 11+

With NestJS 11+, you have to enable the extended query parser, otherwise the filter parser won't work (see migration guide):

async function bootstrap() {
  const app = await NestFactory.create<NestExpressApplication>(AppModule); // <-- Make sure to use <NestExpressApplication>
  app.set('query parser', 'extended'); // <-- Add this line
  await app.listen(3000);
}

Usage - Frontend

First, install all needed types by running

npm i @chax-at/prisma-filter-common

Then, if you want to filter + paginate the result of a certain request, you can send query parameters that satisfy the IFilter interface from the common library.

http://localhost:3000/api/admin/orders?offset=10&limit=10&filter[0][field]=id&filter[0][type]==&filter[0][value]=2&filter[1][field]=name&filter[1][type]=like&filter[1][value]=%Must%&order[0][field]=name&order[0][dir]=asc

Check the FilterOperationType enum to see all possible filter types. Note that by default, all filter values are treated as a string, number (or string[]/number[] for in-filters). If you want to filter by null instead of 'null', then use the EqNull/NeNull filter types (the given value is ignored in this case).

Building a filter

This package provides a FilterBuilder<T> class which can be used to create the filter:

import { FilterBuilder, FilterOperationType } from '@chax-at/prisma-filter-common';

const filterBuilder = new FilterBuilder<User>() // create a new filter builder for User entities..
        .addFilter('name', FilterOperationType.Ilike, '%Max%') // ...filter by name ilike '%Max%'
        .addOrderBy('name', 'asc') // ...order by name, asc
        .setPageSize(40) // ...paginate with a pagesize of 40
        .requestPage(3); // ...return the third page
const filter = filterBuilder.toFilter(); // get the resulting IFilter<User>
const queryString = filterBuilder.toQueryString(); // get the resulting query string (as described below)

// Note that you can also re-use the same filter if you just want to request a different page without changing filter or ordering:
const firstPageFilter = filterBuilder.requestPage(1).toFilter();

Building a query string

In the end, a query string is required which will be sent to the backend server. To build this query string, you can use FilterBuilder.toQueryString() when building a filter using the FilterBuilder as described above. However, it is also possible to transform an existing filter into a query string:

const queryString = FilterBuilder.buildFilterQueryString({
  limit: 20,
  offset: 30,
  filter: [
    { field: 'field1', type: FilterOperationType.NeNull, value: 'val1' },
    { field: 'field2', type: FilterOperationType.InStrings, value: ['str1', 'str2'] },
  ],
  order: [
    { field: 'field1', dir: 'asc' },
    { field: 'field2', dir: 'desc' },
  ],
});
// queryString is
// ?offset=30&limit=20&filter[0][field]=field1&filter[0][type]=nenull&filter[0][value]=val1&filter[1][field]=field2&filter[1][type]=instrings&filter[1][value][]=str1&filter[1][value][]=str2&order[0][field]=field1&order[0][dir]=asc&order[1][field]=field2&order[1][dir]=desc

Filter types

  • Eq, Ne checks for strict (in)equality. Used for numbers and booleans.
  • EqString, NeString string (in)equality check for strings. Does not convert numbers or booleans unlike Eq and Ne.
  • Lt, Lte, Gt, Gte is used to filter numbers by checking whether they are greater/less than (or equal to) the value
  • Contains is transformed into a Prisma contains, used to filter for strings. Use % as a wildcard, e.g. %Max% to find partial matches.
  • IContains is like Contains but case-insensitive
  • StartsWith, EndsWith, Search match the corresponding Prisma operation. Prefix these filters with I if you want to filter case-insensitive (e.g. IStartsWith)
  • In checks whether the value is in the given numbers array. Use InStrings for string arrays.
  • InStrings checks whether value is in the given string array.
  • NotIn checks whether the value is NOT in the given numbers array. Use NotInStrings for string arrays.
  • NotInStrings checks whether value is NOT in the given string array.
  • EqNull, NeNull checks whether the value is null or not null. Must be used instead of Eq, Ne because otherwise null would be treated as string
  • ArrayContains, ArrayStartsWith, ArrayEndsWith can be used on Prisma arrays
  • Has, HasString, HasSome, HasSomeString, HasEvery, HasEveryString

Filter value types

Since the filter is transferred via query parameters, everything will be converted into a string. This library will automatically convert the filter value following these rules:

  • If the filter type is Eq, Ne and the value is 'true' or 'false', then it's converted into a boolean
    • Use EqString, NeString if you want to filter strings and don't convert it
  • If the filter type is not Like or ...String and the value is a number (or a number array for In), then it's converted into a number (or a number array)
  • Otherwise, the value is treated as a string

For string filters, the Like or Ilike filter types are recommended since usually a partial search is required. But if you want to use a different filter for strings, make sure to use the ...String variant of it, otherwise Rachel True can't filter by her name.

Usage - Backend

This package exports two Pipes, the DirectFilterPipe (which is used in most cases) and the more generic FilterPipe. It is also possible to use the exported FilterParser class to transform query parameters manually.

Prerequisites

First, install the package by running

npm i @chax-at/prisma-filter

You also need to have @nestjs/common installed, currently version 6-10 is supported. This package also exports everything from the prisma-filter-common so it is not necessary to install both packages.

To validate the user query input, you might have to provide your own interface implementations with the annotated validation constraints. If you're using class-validator and class-transformer, this definition can look like this (set the constraints and default values for offset+limit to sensible values for your project):

import { FilterOperationType, FilterOrder, GeneratedFindOptions, IFilter, IGeneratedFilter, ISingleFilter, ISingleOrder } from '@chax-at/prisma-filter';
import { Type } from 'class-transformer';
import { IsArray, IsDefined, IsEnum, IsIn, IsInt, IsOptional, IsString, Max, Min, ValidateNested } from 'class-validator';

// The fields are also validated in filter.parser.ts to make sure that only correct fields are used to filter
export class SingleFilter<T> implements ISingleFilter<T> {
  @IsString()
  field!: keyof T & string;

  @IsEnum(FilterOperationType)
  type!: FilterOperationType;

  @IsDefined()
  value: any;
}

export class SingleFilterOrder<T> implements ISingleOrder<T> {
  @IsString()
  field!: keyof T & string;

  @IsIn(['asc', 'desc'])
  dir!: FilterOrder;
}

export class Filter<T = any> implements IFilter<T> {
  @IsArray()
  @ValidateNested({ each: true })
  @Type(() => SingleFilter)
  @IsOptional()
  filter?: Array<SingleFilter<T>>;

  @IsArray()
  @ValidateNested({ each: true })
  @Type(() => SingleFilterOrder)
  @IsOptional()
  order?: Array<SingleFilterOrder<T>>;

  @Type(() => Number)
  @IsInt()
  @Min(0)
  offset = 0;

  @Type(() => Number)
  @IsInt()
  @Min(1)
  @Max(500)
  limit = 100;
}

export class FilterDto<TWhereInput> extends Filter implements IGeneratedFilter<TWhereInput> {
  // This will be set by filter pipe
  findOptions!: GeneratedFindOptions<TWhereInput>;
}

This readme assumes that you're using the file above, but you can adapt the types used in the examples below as needed.

DirectFilterPipe

The direct filter pipe maps fields from the query parameter 1:1 to database fields. This is usually the pipe you want to use.

To enable filtering, you can import and use the DirectFilterPipe<TDto, TWhereInput>(keys, compoundKeys?). Full example:

// Controller
import { Prisma } from '@prisma/client';
// ...

@Controller('/some/path')
export class SomeController {
  constructor(private readonly someService: SomeService) {}

  @Get()
  public async getOrders(
    @Query(new DirectFilterPipe<any, Prisma.OrderWhereInput>(
      ['id', 'status', 'createdAt', 'refundStatus', 'refundedPrice', 'paymentDate', 'totalPrice', 'paymentMethod'],
      ['event.title', 'user.email', 'user.firstname', 'user.lastname', 'contactAddress.firstName', 'contactAddress.lastName'],
    )) filterDto: FilterDto<Prisma.OrderWhereInput>,
  ) {
    return this.someService.getOrders(filterDto.findOptions);
  }
}
// Service

@Injectable()
export class SomeService {
  constructor(private readonly prismaService: PrismaService) {}

  public async getOrders(findOptions: Prisma.OrderFindManyArgs) {
    return this.prismaService.order.findMany({
      ...findOptions,
      // Is is now possible, to add custom options like include
      include: {
        user: true,
      },
      // Note that you cannot simply add `where` here, because you would override the definition from the findOptions
      // Change findOptions.where instead if you want to add additional conditions
    })
  }
}

Generic types

  • TDto is a type that describes the filter query parameter. Can be set to any since the names are mapped 1:1
  • TWhereInput is the target prisma type and types the filterable keys.

Parameters

  • keys is the first parameter and is a list of all keys that can be filtered directly in the OrderWhereInput, not including any relations. These are type checked.
  • compoundKeys (optional) can be used to query related fields, e.g. if your Order model has a relation user, then you can filter on user.email. If the relation is 1:n or n:n like articles in an Order, then you can use the corresponding prisma syntax , e.g. articles.some.title to filter for orders that contain at least one article with the given title. These are not type checked.

Default sort order

Especially when using pagination, you should always define a sort order so that the pagination is stable. You can define a default sort order when creating a DirectFilterPipe that will always be added unless the same key is already defined.

export class SomeController {
  @Get()
  public async getOrders(
    @Query(new DirectFilterPipe<any, Prisma.OrderWhereInput>(
      ['id', 'status', 'createdAt', 'refundStatus', 'refundedPrice', 'paymentDate', 'totalPrice', 'paymentMethod'],
      ['event.title', 'user.email', 'user.firstname', 'user.lastname', 'contactAddress.firstName', 'contactAddress.lastName'],
      // Always add sort by `createdAt` ascending. Use the id as 2nd sorting criteria if the createdAt is equal. 
      [{ createdAt: 'asc' }, { id: 'asc' }],
    )) filterDto: FilterDto<Prisma.OrderWhereInput>,
  ) {
    // ...
  }
}

With the code above, the following requests are possible:

Request orderResulting order
no sorting ordercreatedAt(asc) -> id(asc)
totalPrice(asc)totalPrice(asc) -> createdAt(asc) -> id(asc)
id(desc)id(desc) -> createdAt(asc)

Virtual fields

If you prefix your compoundKey with !, then it will be ignored by the filter pipe. You can use this, if you want to implement some custom logic if a certain filter is set, e.g.

export class SomeController {
  @Get()
  public async getOrders(
    @Query(new DirectFilterPipe<any, Prisma.OrderWhereInput>(
      [],
      ['!paymentInAdvance'],
    )) filterDto: FilterDto<Prisma.OrderWhereInput>,
  ) {
    if(filterDto.filter?.some(f => f.field === '!paymentInAdvance')) {
      console.log('The paymentInAdvance filter is set, now I can do whatever I want!');
    }
  }
}

AllFilterPipeUnsafe

The AllFilterPipeUnsafe is a pipe that can be used more conveniently if you want to allow filtering on all fields of the model. Compound keys still have to be specified as described above.

:warning: This allows users to read ALL keys of the model, even if you don't return the data (e.g. by sending multiple like filters until the user knows the full value).

Make sure that your model does not contain any sensitive data in fields (e.g. don't use this pipe on a users table with a password field).

export class SomeController {
  constructor(private readonly someService: SomeService) {}

  @Get()
  public async getOrders(
    @Query(new AllFilterPipeUnsafe<any, Prisma.OrderWhereInput>(
      ['event.title', 'user.email', 'user.firstname', 'user.lastname', 'contactAddress.firstName', 'contactAddress.lastName', '!paymentInAdvance'],
    )) filterDto: FilterDto<Prisma.OrderWhereInput>,
  ) {
    return this.someService.getOrders(filterDto.findOptions);
  }
}

FilterPipe

The FilterPipe works like the DirectFilterPipe, however the parameter is an object that can map certain query parameter names to different key names of the object, e.g.

{
  // the query parameter is frontendUsernameFilterName, but will filter on the name field of the object
  'frontendUsernameFilterName': 'name',
}

FilterParser

You can use the FilterParser to generate prisma find options without using a pipe.

版本与发布记录

@chax-at/prisma-filter 共发布 26 个版本,首次发布于 2022/03/02;过去 12 个月发布了 1 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
5.0.0latest2026/01/289 个月前
5.0.0-alpha.12025/02/281.7 年前
4.2.02025/02/271.7 年前
4.1.02024/06/142 年前
4.1.0-rc.12024/06/142 年前
4.0.02024/02/212 年前

关于 @chax-at/prisma-filter 的常见问题

@chax-at/prisma-filter 是什么?

The PrismaFilter package allows the frontend to request filtered, sorted, and paginated data via query parameters. The package automatically generates Prisma findOptions from these query parameters, which can then be directly used in the backend.

如何安装 @chax-at/prisma-filter?

在项目目录运行 npm install @chax-at/prisma-filter,或使用 pnpm add / yarn add / bun add。它还需要宿主项目提供 peer 依赖:@nestjs/common(6 - 11)。npm 7 及以上与 pnpm 8 及以上默认会自动安装必需的 peer 依赖,Yarn 需要手动安装。

@chax-at/prisma-filter 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@chax-at/prisma-filter 支持 ESM 还是 CommonJS?

以 CommonJS 形式发布,可以直接 require();在 ES Module 项目中也可以用 import 引入(Node.js 会把 module.exports 作为默认导出)。

@chax-at/prisma-filter 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >= 18。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@chax-at/prisma-filter 可以商用吗?

@chax-at/prisma-filter 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@chax-at/prisma-filter 的下载量有多少?

根据 npm 官方下载统计,@chax-at/prisma-filter 在 2026/10/10 – 2026/11/08 这 30 天内被下载 5,914 次,日均约 197 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@chax-at/prisma-filter 还在维护吗?

最新版本 5.0.0 发布于 2026/01/28(9 个月前);至今共发布 26 个版本,其中过去 12 个月发布了 1 个;首次发布于 2022/03/02。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。