npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ciphercross/nestjs-filters

v1.0.0

Published

NestJS filters library with DTOs, utilities, and enums for pagination, sorting, searching, and filtering. Designed to work seamlessly with Prisma ORM and @ciphercross/nestjs-location for geographic filtering.

Readme

@ciphercross/nestjs-filters

NestJS filters library with DTOs, utilities, and enums for pagination, sorting, searching, and filtering. Designed to work seamlessly with Prisma ORM.

Installation

npm install @ciphercross/nestjs-filters

Peer Dependencies

This library requires the following peer dependencies:

  • @nestjs/common (^10.0.0 || ^11.0.0)
  • @nestjs/swagger (^7.0.0 || ^11.0.0)
  • class-validator (^0.14.0)
  • class-transformer (^0.5.0)

Integration with @ciphercross/nestjs-location

This library works seamlessly with @ciphercross/nestjs-location for geographic filtering. The GeoFiltersDto provides DTO validation, while @ciphercross/nestjs-location handles the actual distance calculations and filtering.

Recommended: Install both packages for complete geographic filtering functionality:

npm install @ciphercross/nestjs-filters @ciphercross/nestjs-location

Features

  • Pagination: Built-in pagination DTOs and utilities
  • Sorting: Sort field validation and Prisma orderBy builders
  • Search: Multi-field search filters with nested field support
  • Date Filtering: Date range filters and time-based filters (today, last 7 days, etc.)
  • Numeric Ranges: Min/max filters for numeric fields
  • Geographic Filters: Latitude/longitude and distance filtering
  • Type-Safe: Full TypeScript support with proper types

Quick Start

1. Create a Custom Filter DTO

Extend BaseListFiltersDto to create your own filter DTO:

import { BaseListFiltersDto, GeoFiltersDto } from '@ciphercross/nestjs-filters';
import { ApiPropertyOptional } from '@nestjs/swagger';
import { IsOptional, IsString } from 'class-validator';

export class ServicesFiltersDto extends BaseListFiltersDto implements GeoFiltersDto {
  // Inherits: page, limit, sortBy, sortOrder, search
  
  // Geo filters
  lat?: number;
  lng?: number;
  maxDistance?: number;

  // Custom filters
  @ApiPropertyOptional()
  @IsOptional()
  @IsString()
  category?: string;

  @ApiPropertyOptional()
  @IsOptional()
  @IsString()
  businessCategory?: string;
}

2. Use in Controller

import { Controller, Get, Query } from '@nestjs/common';
import { ServicesFiltersDto } from './dto/services-filters.dto';

@Controller('services')
export class ServicesController {
  @Get()
  async findAll(@Query() filters: ServicesFiltersDto) {
    // filters.page, filters.limit, filters.search, etc.
  }
}

3. Build Prisma Queries

import {
  buildPagination,
  buildPrismaOrderBy,
  buildSearchFilter,
  buildRangeFilter,
} from '@ciphercross/nestjs-filters';

async getServices(filters: ServicesFiltersDto) {
  // Pagination
  const { skip, take } = buildPagination(filters.page, filters.limit);

  // Sorting
  const orderBy = buildPrismaOrderBy(
    filters.sortBy,
    filters.sortOrder,
    ['createdAt', 'price', 'name'], // allowed fields
    'createdAt', // default field
    'desc' // default order
  );

  // Search
  const searchFilter = buildSearchFilter(filters.search, ['title', 'description']);

  // Numeric range
  const priceFilter = buildRangeFilter('price', filters.minPrice, filters.maxPrice);

  // Build where clause
  const where = {
    ...searchFilter,
    ...priceFilter,
    // ... other filters
  };

  return this.prisma.service.findMany({
    where,
    skip,
    take,
    orderBy,
  });
}

API Reference

DTOs

PaginationFilterDto

Base pagination DTO with page and limit fields.

class PaginationFilterDto {
  page?: number = 1;
  limit?: number = 20;
}

SortFilterDto

Sorting DTO with sortBy and sortOrder fields.

class SortFilterDto {
  sortBy?: string;
  sortOrder?: SortOrder = SortOrder.DESC;
}

SearchFilterDto

Search DTO with search field.

class SearchFilterDto {
  search?: string;
}

GeoFiltersDto

Geographic filters with latitude, longitude, and max distance. Designed to work with @ciphercross/nestjs-location for distance calculations and filtering.

class GeoFiltersDto {
  lat?: number; // -90 to 90
  lng?: number; // -180 to 180
  maxDistance?: number; // 0.1 to 1000 km
}

Note: This DTO only provides validation. For actual distance filtering, use DistanceFilterService from @ciphercross/nestjs-location.

NumericRangeFilterDto

Base numeric range filter with min and max fields.

class NumericRangeFilterDto {
  min?: number;
  max?: number;
}

DateRangeFilterDto

Date range filter with startDate, endDate, and date fields.

class DateRangeFilterDto {
  startDate?: string; // ISO date string
  endDate?: string; // ISO date string
  date?: string; // Single date (ISO date string)
}

BaseListFiltersDto

Combines pagination, sorting, and search. Extend this for your custom filters.

class BaseListFiltersDto extends PaginationFilterDto 
  implements SortFilterDto, SearchFilterDto {
  sortBy?: string;
  sortOrder?: 'asc' | 'desc';
  search?: string;
}

Enums

SortOrder

enum SortOrder {
  ASC = 'asc',
  DESC = 'desc',
}

TimeFilter

enum TimeFilter {
  ALL = 'all',
  TODAY = 'today',
  LAST_7_DAYS = 'last_7_days',
  LAST_30_DAYS = 'last_30_days',
  EARLIER = 'earlier',
}

Utilities

buildPagination(page?, limit?)

Builds Prisma pagination parameters.

const { skip, take } = buildPagination(1, 20);
// Returns: { skip: 0, take: 20 }

buildPrismaOrderBy(sortBy, sortOrder, allowedFields, defaultField, defaultOrder)

Builds Prisma orderBy object with validation.

const orderBy = buildPrismaOrderBy(
  'price',
  'asc',
  ['createdAt', 'price', 'name'],
  'createdAt',
  'desc'
);
// Returns: { price: 'asc' }

buildSearchFilter(searchTerm, fields[])

Builds Prisma search filter for multiple fields.

const searchFilter = buildSearchFilter('spa', ['name', 'description']);
// Returns: {
//   OR: [
//     { name: { contains: 'spa', mode: 'insensitive' } },
//     { description: { contains: 'spa', mode: 'insensitive' } }
//   ]
// }

buildNestedSearchFilter(searchTerm, nestedFields[])

Builds Prisma search filter for nested fields.

const nestedFilter = buildNestedSearchFilter('spa', [
  { path: ['business'], field: 'name' },
  { path: ['category'], field: 'title' }
]);

buildRangeFilter(field, min?, max?)

Builds Prisma numeric range filter.

const priceFilter = buildRangeFilter('price', 10, 100);
// Returns: { price: { gte: 10, lte: 100 } }

buildPrismaDateRangeFilter(field, startDate?, endDate?, singleDate?)

Builds Prisma date range filter.

const dateFilter = buildPrismaDateRangeFilter(
  'createdAt',
  '2024-01-01',
  '2024-12-31'
);
// Returns: { createdAt: { gte: Date, lte: Date } }

buildTimeFilterWhere(field, timeFilter?)

Builds Prisma where clause from TimeFilter enum.

const timeFilter = buildTimeFilterWhere('createdAt', TimeFilter.LAST_7_DAYS);
// Returns: { createdAt: { gte: Date } }

validateSortField(sortBy, allowedFields, defaultField)

Validates sort field against allowed fields.

const field = validateSortField('price', ['createdAt', 'price'], 'createdAt');

validateSortOrder(sortOrder, defaultOrder)

Validates sort order.

const order = validateSortOrder('asc', 'desc');
// Returns: 'asc'

Examples

Example 1: Services with Multiple Filters (with Location)

import {
  BaseListFiltersDto,
  GeoFiltersDto,
  buildPagination,
  buildPrismaOrderBy,
  buildSearchFilter,
  buildRangeFilter,
} from '@ciphercross/nestjs-filters';
import { DistanceFilterService } from '@ciphercross/nestjs-location';

export class ServicesFiltersDto extends BaseListFiltersDto implements GeoFiltersDto {
  lat?: number;
  lng?: number;
  maxDistance?: number;
  minPrice?: number;
  maxPrice?: number;
  category?: string;
}

// In service
@Injectable()
export class ServicesService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly distanceFilterService: DistanceFilterService,
  ) {}

  async getServices(filters: ServicesFiltersDto) {
    // Build Prisma query filters
    const where = {
      ...buildSearchFilter(filters.search, ['title', 'description']),
      ...buildRangeFilter('price', filters.minPrice, filters.maxPrice),
      ...(filters.category && { category: { slug: filters.category } }),
    };

    const orderBy = buildPrismaOrderBy(
      filters.sortBy,
      filters.sortOrder,
      ['createdAt', 'price'],
      'createdAt'
    );

    // Fetch data from database
    const services = await this.prisma.service.findMany({
      where,
      include: { location: true }, // Include location data
    });

    // Apply geographic filtering if coordinates provided
    let filteredServices = services;
    if (filters.lat && filters.lng && filters.maxDistance) {
      filteredServices = this.distanceFilterService.applyDistanceFilter(
        services,
        filters.lat,
        filters.lng,
        filters.maxDistance,
      );
    }

    // Apply pagination after filtering
    const { skip, take } = buildPagination(filters.page, filters.limit);
    const paginatedServices = filteredServices.slice(skip, skip + take);

    return {
      data: paginatedServices,
      total: filteredServices.length,
      page: filters.page || 1,
      limit: filters.limit || 20,
    };
  }
}

Example 2: Visits with Time Filter

import {
  PaginationFilterDto,
  TimeFilter,
  buildTimeFilterWhere,
  buildPagination,
} from '@ciphercross/nestjs-filters';
import { IsOptional, IsEnum, IsString } from 'class-validator';

export class VisitFiltersDto extends PaginationFilterDto {
  @IsOptional()
  @IsEnum(TimeFilter)
  timeFilter?: TimeFilter = TimeFilter.ALL;

  @IsOptional()
  @IsString()
  businessId?: string;
}

// In service
async getVisits(filters: VisitFiltersDto) {
  const { skip, take } = buildPagination(filters.page, filters.limit);
  
  const where = {
    ...buildTimeFilterWhere('createdAt', filters.timeFilter),
    ...(filters.businessId && { businessId: filters.businessId }),
  };

  return this.prisma.visit.findMany({ where, skip, take });
}

Example 3: Reviews with Rating Range

import {
  BaseListFiltersDto,
  NumericRangeFilterDto,
  buildRangeFilter,
} from '@ciphercross/nestjs-filters';

export class ReviewFiltersDto extends BaseListFiltersDto implements NumericRangeFilterDto {
  min?: number; // minRating
  max?: number; // maxRating
  businessId?: string;
}

// In service
async getReviews(filters: ReviewFiltersDto) {
  const { skip, take } = buildPagination(filters.page, filters.limit);
  
  const where = {
    ...buildRangeFilter('rating', filters.min, filters.max),
    ...(filters.businessId && { businessId: filters.businessId }),
  };

  return this.prisma.review.findMany({ where, skip, take });
}

Integration Example: Full Location-Based Search

Complete example using both @ciphercross/nestjs-filters and @ciphercross/nestjs-location:

import { Module } from '@nestjs/common';
import { LocationModule } from '@ciphercross/nestjs-location';
import {
  BaseListFiltersDto,
  GeoFiltersDto,
  buildPagination,
  buildSearchFilter,
} from '@ciphercross/nestjs-filters';
import { LocationService } from '@ciphercross/nestjs-location';

@Module({
  imports: [LocationModule.forRoot()],
})
export class AppModule {}

// DTO
export class BusinessFiltersDto extends BaseListFiltersDto implements GeoFiltersDto {
  lat?: number;
  lng?: number;
  maxDistance?: number;
  category?: string;
}

// Service
@Injectable()
export class BusinessService {
  constructor(
    private readonly prisma: PrismaService,
    private readonly locationService: LocationService,
  ) {}

  async findNearbyBusinesses(filters: BusinessFiltersDto) {
    // 1. Build base Prisma filters
    const where = {
      ...buildSearchFilter(filters.search, ['name', 'description']),
      ...(filters.category && { categories: { some: { slug: filters.category } } }),
    };

    // 2. Fetch businesses with location data
    const businesses = await this.prisma.business.findMany({
      where,
      include: { location: true },
    });

    // 3. Apply location filtering if coordinates provided
    if (filters.lat && filters.lng && filters.maxDistance) {
      const result = this.locationService.findNearest(
        businesses,
        filters.lat,
        filters.lng,
        filters.maxDistance,
        {
          page: filters.page || 1,
          limit: filters.limit || 20,
          sortBy: SortBy.DISTANCE,
        },
      );

      return result;
    }

    // 4. Apply pagination for non-location queries
    const { skip, take } = buildPagination(filters.page, filters.limit);
    return {
      items: businesses.slice(skip, skip + take),
      total: businesses.length,
      page: filters.page || 1,
      limit: filters.limit || 20,
    };
  }
}

License

UNLICENSED