Nestjs-Query

repository·master·Indexed 41 days ago

https://github.com/tripss/nestjs-query

A modular toolkit for NestJS that automates the creation of queryable CRUD APIs with support for complex filtering, sorting, and pagination. It provides a collection of packages for GraphQL and REST interfaces, compatible with multiple ORMs including TypeORM, Sequelize, Mongoose, MikroORM, and Typegoose. Version 10.0.3.

Tokens
100K
Snippets
324
Records
423
Agent score
51%

What's inside Nestjs-Query

  1. Overview of nestjs-query

    master

    Nestjs-Query is a collection of packages designed to help you build queryable CRUD APIs within NestJS applications. It provides reusable primitives for common API patterns such as:

    • Querying: Advanced filtering and searching.
    • Sorting: Ordering results.
    • Paging: Managing result sets via pagination.
    • Authorization: Securing data access.
    • Persistence: Standardized service layers for database interactions.

    It is compatible with both GraphQL and REST architectures.

  2. Overview of nestjs-query features

    master

    nestjs-query is a collection of packages designed to simplify CRUD operations within NestJS applications. It provides standardized patterns for querying, sorting, and paging, particularly for GraphQL transports.

    Key Features:

    • Built on NestJS: Integrates deeply with the NestJS ecosystem.
    • Out-of-the-box GraphQL CRUD: Provides automated resolvers for TypeORM, Sequelize, or Mongoose.
    • Aggregate Queries: Supports aggregations on objects and their related entities.
    • Subscriptions: Built-in support for GraphQL subscriptions on all CRUD operations.
    • Relation Support: Handles one-to-one, one-to-many, many-to-one, and many-to-many relationships.
    • Performance: Includes built-in dataloader support to prevent the n+1 query problem.
    • Relay Compliance: Uses Relay connections for standardized pagination.
  3. Explore GraphQL features in nestjs-query

    master

    The @ptc-org/nestjs-query-graphql package provides base and GraphQL type classes to simplify the creation of code-first GraphQL CRUD applications. Key areas of functionality include:

    • Data Transfer Objects (DTOs): Using DTOs and associated annotations to define data shapes.
    • Resolvers: Utilizing CRUD resolvers for standard operations.
    • Dataloaders: Configuring generated dataloaders for efficient data fetching.
    • Endpoints: Using the provided GraphQL query and mutation endpoints.
    • Hooks: Implementing lifecycle hooks (e.g., beforeCreate).
    • Authorization: Implementing security and access control.
    • Subscriptions: Enabling real-time updates via GraphQL subscriptions.
    • Relations: Specifying relationships within your resolvers.
    • Types: Working with the provided GraphQL types.
    • Federation: Integrating nestjs-query with Apollo Federation.
  4. Configure relations using the @Relation decorator

    master

    When your DTO and Entity are the same class and you have relations defined (specifically in TypeORM or Sequelize), do not use @Field or @FilterableField on the relation property itself. Instead, use the @Relation decorator on the class to expose the relation. This allows for proper handling of nested queries and updates.

    @ObjectType()
    @Relation('todoItem', () => TodoItem, { update: { enabled: true } })
    @Entity({ name: 'sub_task' })
    export class SubTask {
      @IDField(() => ID)
      @PrimaryGeneratedColumn()
      id!: number;
    
      // ... other fields
    
      // Do NOT use @Field or @FilterableField here
      @ManyToOne(() => TodoItem, (td) => td.subTasks, { onDelete: 'CASCADE', nullable: false })
      @JoinColumn({ name: 'todo_item_id' })
      todoItem!: TodoItem;
    }
  5. How unique connection type naming works in v0.15.x

    master

    To allow each relation connection to independently enable the totalCount field, nestjs-query now generates unique connection types for each relation.

    Example Transformation:

    If you have a TodoItem that has a connection to subTasks configured with { enableTotalCount: true }:

    Old GraphQL Output (v0.14.x):

    type TodoItem {
      # ...
      subTasks(paging: CursorPaging = { first: 10 }, filter: SubTaskFilter = {}, sorting: [SubTaskSort!] = []): SubTaskConnection!
    }
    type SubTaskConnection {
      pageInfo: PageInfo!
      edges: [SubTaskEdge!]!
    }

    New GraphQL Output (v0.15.x):

    type TodoItem {
      # ...
      subTasks(
        paging: CursorPaging = { first: 10 }
        filter: SubTaskFilter = {}
        sorting: [SubTaskSort!] = []
      ): TodoItemSubTasksConnection!
    }
    type TodoItemSubTasksConnection {
      pageInfo: PageInfo!
      edges: [SubTaskEdge!]!
      totalCount: Int!
    }
  6. Understand the Query interface structure

    master

    The Query<T> interface is the core mechanism used by REST, GraphQL, and other interfaces in nestjs-query. It consists of three optional fields used to refine data retrieval:

    1. filter: Used to restrict results based on field values.
    2. paging: Used to control the number of results returned (limit and offset).
    3. sorting: Used to define the order of the returned results.

    The Filter interface is typesafe; the TypeScript compiler will prevent you from using fields that do not exist on the target type T.

    import { Query } from '@ptc-org/nestjs-query-core'
    
    interface MyClass {
      title: string
      completed: boolean
      age: number
    }
    
    const q: Query<MyClass> = {
      filter: { /* ... */ },
      paging: { /* ... */ },
      sorting: [ /* ... */ ]
    }
  7. Understand how default filters and authorization filters are combined

    master

    Filters in nestjs-query are sourced from different places and follow specific merging rules. It is critical to distinguish between a Default Filter and an Authorization Filter.

    Default Filter (@QueryOptions)

    • Behavior: Acts as a fallback (GraphQL defaultValue).
    • Logic: If the user provides any filter, the defaultFilter is completely ignored. It is NOT merged with the user's filter.
    • Use case: Providing a sensible default for a common query state (e.g., archived: { is: false }).

    Authorization Filter (@Authorize)

    • Behavior: Always applied.
    • Logic: Always ANDed with the user's filter. Even if the user provides a filter, the auth filter is merged in.
    • Use case: Enforcing security constraints (e.g., ensuring a user only sees items where ownerId matches their ID).
    // Example of an Authorization Filter merge:
    
    // User query:
    // filter: { title: { like: '%todo%' } }
    
    // @Authorize returns:
    // { ownerId: { eq: currentUserId } }
    
    // Actual filter applied to the database:
    // { and: [{ title: { like: '%todo%' } }, { ownerId: { eq: currentUserId } }] }
  8. Change the paging strategy

    master

    By default, nestjs-query uses a cursor based paging strategy returning a Connection object. You can override this using the pagingStrategy option in @QueryOptions with the following values from PagingStrategies:

    • CURSOR (Default): Returns a CursorConnection (Relay-style).
    • OFFSET: Sets paging to allow limit and offset fields, returning an OffsetConnection.
    • NONE: Disables paging entirely, always returning an ArrayConnection.
    import { QueryOptions, PagingStrategies } from '@ptc-org/nestjs-query-graphql'
    
    @QueryOptions({ pagingStrategy: PagingStrategies.OFFSET })
    export class TodoItemDTO { /* ... */ }
  9. Understand changes to GraphQL filter definitions in v0.16.x

    master

    Starting in v0.16.x, filters are no longer global for a type in GraphQL. Instead, filters are defined based on the specific operation being performed: read, update, or delete.

    This change was implemented to support filtering on relations. As a result, your generated GraphQL schema will contain new, distinct types for each filter operation (e.g., separate input types for reading vs. updating).

  10. How filters flow through the system

    master

    The lifecycle of a filter from a GraphQL request to the database follows this sequence:

    1. GraphQL Query: The user provides a filter argument.
    2. Default Filter: If the user filter is empty, the defaultFilter from @QueryOptions is applied.
    3. Resolver: The resolver receives the query.
    4. Auth Filter: The @Authorize filter is merged with the existing filter using AND logic.
    5. QueryService: The merged filter is passed to QueryService.query().
    6. ORM: The filter is translated by the configured ORM (TypeORM, Sequelize, or Mongoose).
    7. Database: The final query is executed against the database.
  11. Filter relations using authorization

    master

    By default, when querying a relation, nestjs-query automatically applies any additional filters defined via the @Authorize decorator on the relation's DTO.

    For example, if SubTaskDTO has an @Authorize filter restricting ownerId, querying subTasks through a TodoItem will only return sub-tasks belonging to the authenticated user.

    @ObjectType('SubTask')
    @Authorize({ authorize: (context: UserContext) => ({ ownerId: { eq: context.req.user.id } }) })
    @FilterableRelation('todoItem', () => TodoItemDTO, { update: { enabled: true } })
    export class SubTaskDTO {
      // ...
    }