You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何从Query Params优雅构建NestJS+Prisma筛选对象?

NestJS + Prisma + PostgreSQL 构建筛选Query Params的最佳实践

在NestJS结合Prisma操作PostgreSQL的场景中,从可选Query Params构建符合Prisma规则的筛选对象,可通过以下几种方案避免代码冗余混乱:

方案1:DTO+自定义管道统一处理

通过定义筛选DTO和专用管道,将参数转换逻辑集中管理,控制器代码更简洁。

1.1 定义筛选DTO

import { PropertyType } from '@prisma/client';

export class HomeFilterDto {
  city?: string;
  minPrice?: string;
  maxPrice?: string;
  numberOfBedrooms?: string;
  numberOfBathrooms?: string;
  minLandSize?: string;
  maxLandSize?: string;
  propertyType?: PropertyType;
}

1.2 实现自定义转换管道

import { PipeTransform, Injectable } from '@nestjs/common';
import { HomeFilterDto } from './home-filter.dto';
import { PropertyType } from '@prisma/client';

@Injectable()
export class HomeFilterPipe implements PipeTransform<HomeFilterDto, any> {
  transform(value: HomeFilterDto) {
    const filters: any = {};

    // 基础字符串字段
    if (value.city) filters.city = value.city;
    if (value.propertyType) filters.propertyType = value.propertyType;

    // 数字转换字段
    if (value.numberOfBedrooms) {
      filters.numberOfBedrooms = parseInt(value.numberOfBedrooms);
    }
    if (value.numberOfBathrooms) {
      filters.numberOfBathrooms = parseInt(value.numberOfBathrooms);
    }

    // 处理price范围筛选
    const priceConditions: any = {};
    if (value.minPrice) priceConditions.gte = parseFloat(value.minPrice);
    if (value.maxPrice) priceConditions.lte = parseFloat(value.maxPrice);
    if (Object.keys(priceConditions).length > 0) {
      filters.price = priceConditions;
    }

    // 处理landSize范围筛选
    const landSizeConditions: any = {};
    if (value.minLandSize) landSizeConditions.gte = parseInt(value.minLandSize);
    if (value.maxLandSize) landSizeConditions.lte = parseInt(value.maxLandSize);
    if (Object.keys(landSizeConditions).length > 0) {
      filters.landSize = landSizeConditions;
    }

    return filters;
  }
}

1.3 控制器中使用

import { Controller, Get, Query, UsePipes } from '@nestjs/common';
import { HomeFilterDto } from './home-filter.dto';
import { HomeFilterPipe } from './home-filter.pipe';

@Controller('homes')
export class HomeController {
  constructor(private readonly prisma: PrismaService) {}

  @Get()
  @UsePipes(HomeFilterPipe)
  async getHomes(@Query() filters: any) {
    return this.prisma.home.findMany({ where: filters });
  }
}

方案2:Class-Transformer + Class-Validator 自动转换

利用NestJS生态的class-validator和class-transformer,实现参数校验与类型转换,再通过管道生成Prisma筛选结构。

2.1 带转换规则的DTO

import { PropertyType } from '@prisma/client';
import { Transform } from 'class-transformer';
import { IsOptional, IsEnum, IsString, IsNumberString } from 'class-validator';

export class HomeFilterDto {
  @IsOptional()
  @IsString()
  city?: string;

  @IsOptional()
  @IsNumberString()
  minPrice?: string;

  @IsOptional()
  @IsNumberString()
  maxPrice?: string;

  @IsOptional()
  @IsNumberString()
  @Transform(({ value }) => parseInt(value))
  numberOfBedrooms?: number;

  @IsOptional()
  @IsNumberString()
  @Transform(({ value }) => parseInt(value))
  numberOfBathrooms?: number;

  @IsOptional()
  @IsNumberString()
  minLandSize?: string;

  @IsOptional()
  @IsNumberString()
  maxLandSize?: string;

  @IsOptional()
  @IsEnum(PropertyType)
  propertyType?: PropertyType;
}

2.2 转换为Prisma筛选的管道

import { PipeTransform, Injectable } from '@nestjs/common';
import { HomeFilterDto } from './home-filter.dto';

@Injectable()
export class PrismaFilterPipe implements PipeTransform<HomeFilterDto, any> {
  transform(dto: HomeFilterDto) {
    const filters: any = {};

    Object.assign(filters, {
      ...(dto.city && { city: dto.city }),
      ...(dto.numberOfBedrooms && { numberOfBedrooms: dto.numberOfBedrooms }),
      ...(dto.numberOfBathrooms && { numberOfBathrooms: dto.numberOfBathrooms }),
      ...(dto.propertyType && { propertyType: dto.propertyType }),
    });

    // 处理price范围
    const price = {};
    if (dto.minPrice) price['gte'] = parseFloat(dto.minPrice);
    if (dto.maxPrice) price['lte'] = parseFloat(dto.maxPrice);
    if (Object.keys(price).length) filters.price = price;

    // 处理landSize范围
    const landSize = {};
    if (dto.minLandSize) landSize['gte'] = parseInt(dto.minLandSize);
    if (dto.maxLandSize) landSize['lte'] = parseInt(dto.maxLandSize);
    if (Object.keys(landSize).length) filters.landSize = landSize;

    return filters;
  }
}

2.3 控制器中使用

import { Controller, Get, Query, UsePipes, ValidationPipe } from '@nestjs/common';
import { HomeFilterDto } from './home-filter.dto';
import { PrismaFilterPipe } from './prisma-filter.pipe';

@Controller('homes')
export class HomeController {
  constructor(private readonly prisma: PrismaService) {}

  @Get()
  @UsePipes(new ValidationPipe({ transform: true }), PrismaFilterPipe)
  async getHomes(@Query() filters: any) {
    return this.prisma.home.findMany({ where: filters });
  }
}

方案3:工具函数动态构建筛选对象

抽离通用工具函数,将重复的参数判断、类型转换逻辑封装,控制器直接调用生成筛选结构。

3.1 工具函数实现

// src/utils/prisma-filter.util.ts
import { PropertyType } from '@prisma/client';

type HomeQueryParams = {
  city?: string;
  minPrice?: string;
  maxPrice?: string;
  numberOfBedrooms?: string;
  numberOfBathrooms?: string;
  minLandSize?: string;
  maxLandSize?: string;
  propertyType?: PropertyType;
};

export function buildHomeFilters(params: HomeQueryParams) {
  const filters: any = {};

  // 基础字段处理
  addIfPresent(filters, 'city', params.city);
  addIfPresent(filters, 'propertyType', params.propertyType);

  // 数字转换字段处理
  addIfPresent(filters, 'numberOfBedrooms', params.numberOfBedrooms, val => parseInt(val));
  addIfPresent(filters, 'numberOfBathrooms', params.numberOfBathrooms, val => parseInt(val));

  // 范围筛选字段处理
  addRangeFilter(filters, 'price', params.minPrice, params.maxPrice, val => parseFloat(val));
  addRangeFilter(filters, 'landSize', params.minLandSize, params.maxLandSize, val => parseInt(val));

  return filters;
}

// 通用工具:值存在则添加,支持转换
function addIfPresent(obj: any, key: string, value: any, transform?: (val: any) => any) {
  if (value != null) {
    obj[key] = transform ? transform(value) : value;
  }
}

// 通用工具:添加范围筛选条件
function addRangeFilter(obj: any, field: string, minVal: any, maxVal: any, transform?: (val: any) => any) {
  const range: any = {};
  if (minVal != null) range.gte = transform ? transform(minVal) : minVal;
  if (maxVal != null) range.lte = transform ? transform(maxVal) : maxVal;
  if (Object.keys(range).length > 0) {
    obj[field] = range;
  }
}

3.2 控制器中使用

import { Controller, Get, Query } from '@nestjs/common';
import { buildHomeFilters } from './utils/prisma-filter.util';

@Controller('homes')
export class HomeController {
  constructor(private readonly prisma: PrismaService) {}

  @Get()
  async getHomes(@Query() params) {
    const filters = buildHomeFilters(params);
    return this.prisma.home.findMany({ where: filters });
  }
}

注:无需编写原生SQL,以上方案均基于Prisma的Query API构建筛选条件,符合Prisma的where参数规则。

内容的提问来源于stack exchange,提问作者danulqua

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 01:36:01