Skip to content

querry-kit/nest

Repository files navigation

@querry-kit/nest

npm npm downloads license node bundle size TypeScript Buy Me a Coffee

build test coverage lint docs changesets npm publish

Consolidated NestJS helpers for Query Kit APIs: fields projection, Prisma-style query services, CASL policies, OpenAPI decorators, pipes, pagination DTOs, and object utilities.

πŸ“– Documentation: https://querry-kit.github.io/nest/

🌐 Querry Kit Ecosystem

The Querry Kit overview connects the three core packages and the Workboard reference application:

πŸ“š Table of Contents

πŸ“¦ Install

pnpm add @querry-kit/nest
pnpm add @nestjs/common @nestjs/core @nestjs/swagger class-transformer class-validator reflect-metadata

For the optional CASL adapter:

pnpm add @casl/ability @casl/prisma

The current package version is published on npm. npm is the primary distribution channel.

GitHub release tags remain available as a fallback:

pnpm add github:querry-kit/nest#v0.0.1

πŸš€ Release Workflow

Releases are driven by Changesets and GitHub Actions. The main branch does not contain committed dist files; it only contains source, documentation, examples, and workflow configuration.

Package-visible changes should include a changeset:

pnpm changeset

When changes land on main, the changesets workflow creates or updates a release PR. That PR contains the version bump and changelog updates produced by:

pnpm changeset version

The npm publish workflow uses npm Trusted Publishing through GitHub Actions OIDC. The npm package must be connected to this repository and workflow in the npm package publishing settings:

  • Repository: querry-kit/nest
  • Workflow file: release.yml
  • Environment: unset

After the release PR is merged, the npm publish workflow runs for the version/changelog changes. It runs package checks, builds dist in CI, publishes @querry-kit/nest to npm, tags the release commit as vX.Y.Z, and creates a GitHub Release.

Consumers should install from npm:

pnpm add @querry-kit/nest

🧩 Usage

ResourceQuery covers the common controller flow: parse optional fields, merge endpoint-required includes with client include values, generate Prisma includes for selected relations, call a QueryService, map models to DTOs, and project the response.

import { Get, Query, Req } from '@nestjs/common';
import { ApiErrorResponses, ApiPaginatedResponse, ApiResourceQuery, QueryDTO, ResourceQuery } from '@querry-kit/nest';

@Get()
@ApiResourceQuery()
@ApiPaginatedResponse({ model: CustomerDTO })
@ApiErrorResponses({ badRequestDescription: 'Invalid query parameter.' })
async query(@Req() req: AuthRequest, @Query() query: QueryDTO<CustomerTypeMap>) {
  return ResourceQuery.query({
    service: this.customersService,
    query,
    schema: CustomerDTO,
    ability: req.ability,
    map: (customer, ability) => CustomerDTO.fromModel(customer, ability),
  });
}

Create resource services with QueryService and a Prisma-compatible delegate:

import { Injectable } from '@nestjs/common';
import { QueryService, type BaseDelegateTypeMap } from '@querry-kit/nest';
import { Prisma, PrismaService } from '../prisma';

interface CustomerTypeMap extends BaseDelegateTypeMap {
  select: Prisma.CustomerSelect;
  include: Prisma.CustomerInclude;
  whereInput: Prisma.CustomerWhereInput;
  orderByWithRelationInput: Prisma.CustomerOrderByWithRelationInput;
  whereUniqueInput: Prisma.CustomerWhereUniqueInput;
  scalarFieldEnum: Prisma.CustomerScalarFieldEnum;
}

@Injectable()
export class CustomersService extends QueryService<typeof PrismaService.prototype.customer, CustomerTypeMap> {
  constructor(prisma: PrismaService) {
    super(prisma.customer);
  }
}

Focused subpaths are available for smaller imports:

import { createCaslAccessibleWhere, filterCaslFields } from '@querry-kit/nest/casl';
import { ApiResourceQuery } from '@querry-kit/nest/decorators';
import { Fields } from '@querry-kit/nest/fields';
import { parseObject } from '@querry-kit/nest/object';
import { QueryService } from '@querry-kit/nest/query-service';

πŸ” CASL

CASL is optional. Pass ability only when the endpoint should merge an authorization-aware where clause through QueryService; omit it for APIs that do not use CASL.

import { createCaslAccessibleWhere } from '@querry-kit/nest/casl';

super(prisma.customer, {
  subject: 'Customer',
  accessibleWhere: createCaslAccessibleWhere({ action: 'read' }),
});

When an ability is passed to query, the service combines the CASL where clause with user filters as { AND: [accessibleWhere, parsedWhere] }.

For field-level response permissions, filter the completed DTO without mutating it:

return filterCaslFields(dto, 'Customer', ability);

The helper uses the read action by default. Pass { action: RoleAction.READ } when an application uses uppercase or enum-backed actions. It filters serialized DTO fields only; keep passing the ability to QueryService to constrain database reads too.

🎯 Fields

fields is optional by default. When omitted, Query Kit returns the complete DTO response. When present, it validates the syntax and selected fields, adds required relation includes, and projects the returned DTOs. Outer braces are optional, so {id,title} is equivalent to id,title.

GET /books?fields=id,title
GET /books?fields={id,title}
GET /books?fields=items{id,title},meta{page,perPage,itemCount,pageCount}
GET /books?fields=items{},meta{page}

An explicit empty value (?fields=) or empty outer selection (?fields={}) returns {}. Empty nested selections are also valid: items{} produces empty item objects, while meta{page} keeps only the requested page metadata. A value containing only whitespace remains invalid.

Use prepareFieldsQuery directly when a controller needs custom service orchestration:

import { Fields, prepareFieldsQuery } from '@querry-kit/nest/fields';

const prepared = prepareFieldsQuery({
  fields: query.fields,
  include: query.include,
  schema: BookDTO,
  requiredInclude: { author: true },
});

const { items, pageMeta } = await booksService.query({ ...query, include: prepared.include });

return {
  items: Fields.project(items.map(BookDTO.fromModel), prepared.projection),
  meta: Fields.project(pageMeta, prepared.metaProjection),
};

πŸ§ͺ Example API

A small in-memory NestJS example lives in examples/books-api. It uses books and authors to show relation includes, fields projection, pagination metadata, CASL-aware controller flow, query transformation, and OpenAPI documentation.

pnpm examples:check
pnpm examples:build

πŸ“– Documentation

Run the VitePress documentation locally:

pnpm docs:dev

Build the documentation:

pnpm docs:build

πŸ›  Development

pnpm install
pnpm lint
pnpm check
pnpm test
pnpm test:coverage
pnpm build
pnpm examples:check
pnpm examples:build
pnpm docs:build

pnpm test:coverage collects all source files, prints the coverage summary, and writes HTML and LCOV reports to coverage/. GitHub Actions runs the same command and retains the report as a workflow artifact.

About

Consolidated NestJS helpers for Query Kit APIs: fields projection, Prisma-style queries, CASL policies, OpenAPI decorators, pipes, pagination, and object utilities.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Sponsor this project

Contributors