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

@gabrieljsilva/nest-graphql-filters

v0.0.6

Published

Generate filters in a simple, automatic, and standardized way in the @nestjs framework and @nestjs/graphql using decorators.

Readme

Nest Graphql Filters

Generate filters in a simple, automatic and standardized way in the @nestjs framework and @nestjs/graphql using decorators.

Installation

npm install @gabrieljsilva/nest-graphql-filters

or

yarn add @gabrieljsilva/nest-graphql-filters

Usage

First, it's necessary to register the module, where the registration method requires a mandatory param called databaseProvider. This information is crucial in the subsequent serialization process. Ex:

import { NestFilterModule } from '@gabrieljsilva/nest-graphql-filters';

@Module({
  imports: [
    GraphQLModule.forRoot<ApolloDriverConfig>({
      driver: ApolloDriver,
      autoSchemaFile: join(process.cwd(), 'schema.gql')
    }),
    NestFilterModule.register('pg'),
    // rest of imports...
  ],
  controllers: [],
  providers: [],
  exports: [],
})
export class AppModule {}

After registering the module, the second step is to declare the entities using a class and applying the decorators: FilterableEntity and FilterableField.

  • FilterableEntity: Marks a class as an entity.
  • FilterableField: Marks a class property as a "filterable" field.

ex:

import { ID } from '@nestjs/graphql';
import { FilterableEntity, FilterableField } from '@gabrieljsilva/nest-graphql-filters';

@FilterableEntity()
export class User {
  @FilterableField(() => ID)
  id: string;
  
  @FilterableField()
  name: string;

  @FilterableField()
  email: string;
  
  password: string;
    
  @FilterableField()
  createdAt: Date;
  
  @FilterableField()
  deletedAt?: Date;
}

To apply this filter in a Resolver, use the FilterArgs decorator, passing the target entity as an argument. It's possible to infer type using "FilterOf" generic. Ex:

import { Query, Resolver } from '@nestjs/graphql';
import { FilterArgs, FilterOf } from '@gabrieljsilva/nest-graphql-filters';

import { UserService } from './user.service';
import { User } from '../../models';

@Resolver(User)
export class UserResolver {
    constructor(private readonly userService: UserService) {}
    
    @Query(() => [User])
    async findUsers(@FilterArgs(User) userFilter: FilterOf<User>) {
        return this.userService.findUsers(userFilter);
    }
}

Operations

Each primitive type has an associated filter with its own operators:

| | Boolean | ID | Int | Float | String | Date | Timestamp | |----------|---------|-----|-----|-------|--------|------|-----------| | Is | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | | Like | ✕ | ✕ | ✕ | ✕ | ✓ | ✕ | ✕ | | In | ✕ | ✕ | ✓ | ✓ | ✓ | ✓ | ✓ | | Gt | ✕ | ✕ | ✓ | ✓ | ✕ | ✓ | ✓ | | Lt | ✕ | ✕ | ✓ | ✓ | ✕ | ✓ | ✓ | | Gte | ✕ | ✕ | ✓ | ✓ | ✕ | ✓ | ✓ | | Lte | ✕ | ✕ | ✓ | ✓ | ✕ | ✓ | ✓ |

Also, each entity is created with the following logical operators:

| Name | Description | |----------|----------------------------------------------------| | _AND | A list of entities that perform an "AND" operation | | _OR | A list of entities that perform an "OR" operation | | _NOT | A list of entities that perform an "NOT" operation |

Querying

Through the previously defined User entity and the findUsers query, it is possible to construct the following GraphQL query:

Example Query

query FindUsers($filter: UserFilter) {
    findUsers(filters: $filter){
        id
        name
        email
        createdAt
        deletedAt
    }
}

Example Variables

{
  "filters": {
    "name": {
      "like": "John"
    },
    "createdAt": {
      "gte": "2024-01-01" 
    },
    "_NOT": [
      {
        "name": {
          "is": "John Doe"
        }
      }
    ]
  }  
}

Relationships

In addition to filters based on primitive types, it is also possible to create relationships between entities. Ex:

import { ID } from '@nestjs/graphql';
import { FilterableEntity, FilterableField } from '@gabrieljsilva/nest-graphql-filters';

@FilterableEntity()
export class Photo {
    @FilterableField(() => ID)
    id: string;

    @FilterableField()
    url: string;
    
    @FilterableField()
    category: string
}

@FilterableEntity()
export class User {
  @FilterableField(() => ID)
  id: string;
  
  @FilterableField()
  name: string;

  @FilterableField()
  photo: Photo
}

Example Query

query FindUsers($filter: UserFilter) {
    findUsers(filters: $filter){
        id
        name
        photo {
            id
            url
            category
        }
    }
}

Example Variables

{
  "filters": {
    "email": {
      "is": "[email protected]"
    },
    "photo": {
      "category": {
        "is": "Nature"
      }  
    }
  }  
}

Circular Dependency

Although not recommended, it is still possible to create circular types (types that depend on each other simultaneously).

import { ID } from '@nestjs/graphql';
import { FilterableEntity, FilterableField } from '@gabrieljsilva/nest-graphql-filters';

@FilterableEntity()
export class Credentials {
    @FilterableField(() => ID)
    id: string;

    @FilterableField()
    email: string;

    password: string;

    @FilterableField(() => User)
    user: User
}

@FilterableEntity()
export class User {
    @FilterableField(() => ID)
    id: string;

    @FilterableField()
    name: string;

    @FilterableField()
    Credentials: Credentials
}

PS: The SWC compiler does not correctly load circular dependencies.

Serialization

It's possible to use pipes to serialize the data received in the request. Ex:

import { FilterOptions } from '@gabrieljsilva/nest-graphql-filters';

export const ToPrismaQueryPipe = memoize<(type: Type) => PipeTransform>(
    createToPrismaQueryPipe,
);

function createToPrismaQueryPipe(type: Type): Type<PipeTransform> {
    class ToPrismaQueryPipe implements PipeTransform {
        constructor(@Inject(FilterOptions) filterOptions: FilterOptions) {}

        async transform<T = unknown>(value: FilterOf<T>) {
            if (!value) {
                return {};
            }
            const fieldMetadata = FilterTypeMetadataStorage.getIndexedFieldsByType(type);
            return this.getWhereInputQuery(value, fieldMetadata);
        }

        getWhereInputQuery(
            filter: FilterOf<unknown>,
            metadata: Map<string, FieldMetadata>,
            query = {},
        ) {
            // Implements your query here
        }
    }

    return ToPrismaQueryPipe;
}

The property filterOptions contains the property named provider, which is the name of the database provider. Since even among relational databases that use SQL, there are some differences in query construction, this information can assist in writing more generic pipes and facilitate the database switch at some point.

To use pipes with parameters, it's necessary to create a function that returns the pipe class, or it's instance. By using this method, a new pipe is created for each implementation of the filter. To prevent redundancy in pipe creation, the memoization technique can be applied, ensuring the use of the same pipe even when implemented in multiple different queries.

It's possible to create more complex pipes by utilizing metadata extracted from the following functions:

| Name | Return Type | description | |--------------------|----------------------------|------------------------------------------------------------| | getFieldMetadata | Set | Returns a set of FieldMetadata. | | getIndexedFields | Map<string, FieldMetadata> | Returns a Map where the keys are the names of the Schemas. |

Both functions have different goals. The getFieldMetadata function aims to be iterable and presents a complexity of O(N), while the getIndexedFields function aims to be indexed by name to avoid iterations and optimize performance in runtime with a complexity of O(1).

FieldMetadata

| Property | Type | Description | |-------------------|-----------|------------------------------------| | name | string | Name of the property (public) | | originalName | string | Original name of the property | | type | Type | Property original type | | isPrimitiveType | boolean | Whether type is a primitive | | nullable | boolean | Whether the field is nullable | | isArray | boolean | Whether the property is an array | | description | string? | Custom description of the property |

PS: Marking a property as "array" or "nullable" does not imply that the generated filter property by the library is an "array" or "null", refers only to the original entity property. Marking a field as "nullable" or "array" serves to guide pipes to the exact structure of the original entity.

Applying pipes

import { ToPrismaQueryPipe } from '../../pipes';

@Resolver(User)
export class UserResolver {
    constructor(private readonly userService: UserService) {}

    @Query(() => [User])
    async findUsers(@FilterArgs(User, ToPrismaQueryPipe(User)) userFilter: FilterOf<User>) {
        return this.userService.findUsers(userFilter);
    }
}

Customization

It's possible to make some customizations to the generated types or metadata through decorators:

// Defines a custom name for the type that will be exposed by the API
@FilterableEntity('CustomUserFilterName')
export class User {
    @FilterableField(() => ID)
    id: string;

    // Defines a custom name and description for the property that will be exposed by the API 
    // and defines in the metadata that this property is not nullabe
    @FilterableField({ name: 'customUserName', description: 'My Awesome Name', nullable: false })
    name: string;

    // Defines in the metadata that this field is an array of a specific type
    @FilterableField(() => [Photo])
    photo: Photo[]

    // Explicitly defines type and options
    @FilterableField(() => Address, { name: 'myAddress', nullable: true })
    address?: Address
}