nestjs-query-core
Framework-independent query, filter, sort, and service primitives powering NestJS Query's CRUD API generation.
Repository Health
Technical Analysis
@ptc-org/nestjs-query-core is the foundational package in the NestJS Query ecosystem, providing the framework-independent types and services that every persistence adapter (TypeORM, Sequelize, Mongoose, MikroORM, Typegoose) and every transport layer (GraphQL, REST) build on. It defines the QueryService interface, the Filter/Query/Paging types used to describe user requests, and helper functions (applyFilter, applySort, applyPaging, mergeFilter) that implementations use to normalize query input consistently.
Rather than talking to a database directly, the core package establishes contracts: a QueryService for CRUD/aggregate operations, an Assembler for mapping between DTOs and persistence entities, and a NestjsQueryCoreModule for wiring assemblers into NestJS’s dependency injection container. Downstream packages (nestjs-query-graphql, nestjs-query-rest, nestjs-query-typeorm, etc.) implement these contracts for a specific ORM or transport, so applications get consistent filtering, sorting, paging, and relation-loading behavior no matter which persistence layer they choose.
What You Get
- QueryService interface - a persistence-agnostic contract (query, aggregate, count, findById, queryRelations, createMany, updateMany, deleteMany) that every ORM adapter package implements identically.
- Assembler system - AbstractAssembler/DefaultAssembler/ClassTransformerAssembler classes plus AssemblerFactory for converting between API-facing DTOs and persistence entities without manual mapping code.
- Filter and Query builders - typed Filter, Query, Paging, and AggregateQuery interfaces with helpers (applyFilter, applySort, applyPaging, mergeFilter, transformAggregateQuery) for composing complex CRUD queries.
- NestjsQueryCoreModule - a NestJS DynamicModule (forFeature) that registers assemblers and their query services into Nest’s DI container.
- RelationQueryService and ProxyQueryService - reusable service wrappers for relation loading and delegating/decorating an existing QueryService implementation.
Common Use Cases
- Building a new ORM adapter for NestJS Query by implementing the QueryService interface against a database driver not yet supported.
- Writing a custom Assembler to expose a GraphQL/REST DTO shape that differs from the underlying database entity (renamed fields, computed properties, aggregated sub-resources).
- Composing filter and sort logic programmatically in application code (e.g. saved-search or reporting features) using the same Filter/Query types the generated resolvers use.
- Wrapping an existing QueryService with ProxyQueryService to add cross-cutting behavior (auditing, caching, soft authorization) without modifying the underlying persistence implementation.
Under The Hood
Architecture The core package defines a layered contract system rather than any concrete persistence logic: QueryService (services/query.service.ts) is the central interface that every ORM adapter package (nestjs-query-typeorm, nestjs-query-sequelize, nestjs-query-mongoose, nestjs-query-mikro-orm, nestjs-query-typegoose) implements identically, while ProxyQueryService and RelationQueryService (services/proxy-query.service.ts, services/relation-query.service.ts) decorate any QueryService implementation to add relation-loading behavior without touching the underlying adapter. DTO-to-entity mapping is handled by the Assembler abstraction (assemblers/abstract.assembler.ts, default.assembler.ts, class-transformer.assembler.ts), resolved at runtime by AssemblerFactory.getAssembler, which walks a DTO’s registered assembler map and falls back to inheritance-based matching or a DefaultAssembler identity mapping. NestjsQueryCoreModule.forFeature (module.ts) wires assemblers and their generated query-service providers into Nest’s dependency-injection container via createServices (providers.ts). Because every consuming package only ever depends on these interfaces — never on a concrete ORM — replacing the persistence layer under an existing GraphQL/REST API is a matter of swapping which adapter package supplies the QueryService implementation.
Tech Stack The package is plain TypeScript with almost no runtime dependencies of its own (lodash.merge, reflect-metadata, tslib) and peer-depends only on @nestjs/common and class-transformer — it has no database driver, GraphQL, or REST dependency, which is the point: it’s the framework-independent base that nestjs-query-graphql, nestjs-query-rest, and each ORM-specific package build on. The monorepo is managed with Nx and Yarn workspaces, compiled per-package via dedicated tsconfig files, and tested with Jest. CI runs through GitHub Actions (test, CodeQL, and release workflows), with automated semantic-versioned releases and Renovate keeping dependencies current.
Code Quality Test coverage is extensive and mirrors the source layout one-to-one: dedicated test directories for services, assemblers, decorators, and interfaces each hold focused spec files, run via Jest and reported to Codecov. The codebase is fully typed with generics used deliberately (QueryService<DTO, C, U>, Assembler<DTO, Entity, C, CE, U, UE>) to keep DTO/entity/create/update types distinct through the call chain, and ESLint plus Prettier enforce style, with no-explicit-any exceptions called out only where TypeScript’s generic variance genuinely can’t express the overload. CodeQL scanning and a Snyk vulnerability badge back the security posture, and a CONTRIBUTING guide documents the expected PR/commit workflow.
API Design The public API favors small, composable interfaces over a single monolithic service: Filter, Query, Paging, SortField, and AggregateQuery are plain typed objects that helper functions (applyFilter, applySort, applyPaging, mergeFilter, transformAggregateQuery) operate on, so consumers can build queries programmatically without instantiating any class. Getting started requires implementing one interface (QueryService) and, only if DTOs diverge from entities, one Assembler subclass — the DefaultAssembler covers the common case where DTO and entity shapes match, keeping required boilerplate minimal. Naming stays consistent with the wider NestJS ecosystem (decorator-driven, forFeature module pattern), which lowers the learning curve for developers already familiar with Nest, though the layered generic signatures demand real familiarity with the type system to extend correctly.