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

restql-nest

v2.0.3

Published

A dynamic RESTful query framework for NestJS applications using Prisma and PostgreSQL.

Readme

📚 RestQL Framework Documentation (Backend - NestJS/Prisma)

RestQL is a powerful utility framework designed for NestJS applications utilizing PostgreSQL and Prisma. It transforms the common backend architecture from multiple, static GET endpoints into a single, highly flexible, dynamic query endpoint. This allows frontend clients to request only the exact fields and relations they need, drastically reducing over-fetching and optimizing API performance.


1. Core Integration Setup

The RestQL functionality is primarily handled within the Controller and Service layers using custom pipes, decorators, and guards.

1.1. Controller Integration

The GET endpoint must be configured to accept a dynamic query parameter object (RestqlQueryDto) and apply validation and access control.

Example: CategoryController

TypeScript

@Controller('category')
export class CategoryController {
  constructor(private readonly categoryService: CategoryService) {}

  @Get()
  @RestqlGuard(['include:updatedByAdmin'], JwtAuthGuard) // 🛡️ Security Guard
  @UsePipes(new RestqlQueryValidationPipe('Category')) // ✅ Input Validation
  find(
    @Query() query: RestqlQueryDto // 🧱 Query DTO (auto-transformed)
  ) {
    return this.categoryService.find(query);
  }
}

| Component | Purpose | Explanation | | :--- | :--- | :--- | | @Query() query: RestqlQueryDto | Input | Receives the raw query string (?fields=...&include=...). The decorators on RestqlQueryDto automatically parse and transform these strings into structured objects. | | @UsePipes(new RestqlQueryValidationPipe('ModelName')) | Validation | Verifies that all requested fields and includes are valid Prisma field names for the specified model ('Category'). Also validates the data type of filter values (e.g., ensuring a Number filter value is actually a number). | | @RestqlGuard([..], Guard) | Security | A declarative way to apply a Guard (e.g., JwtAuthGuard) only if a specific, sensitive field or relation is requested by the client. |

1.2. Service Integration

The service is responsible for receiving the parsed query object and translating it into a native Prisma query object.

Example: CategoryService

TypeScript

@Injectable()
export class CategoryService {
  constructor(private readonly prisma: PrismaService) {}

  async find(query: RestqlQueryDto) {
    const { fields, include, limit} = query;

    return findWithRestql(
      this.prisma.category, // 1. Prisma Model (e.g., client.category)
      fields,               // 2. Fields to select
      include,              // 3. Relations to include
      'Category',           // 4. Model Name for logging/context
      limit                 // 5. Pagination limit
    );
  }
}

The findWithRestql utility function handles the conversion of the structured RestqlQueryDto (which contains fields, filters, and includes) into the final { select: { ... }, include: { ... }, where: { ... }, take: ... } Prisma query object.


2. Dynamic Query Features & Syntax

The client uses standard HTTP query parameters, which the backend transforms into a full Prisma query.

2.1. Field Selection (fields)

Client requests a comma-separated list of fields. Fields can be included, explicitly excluded (prefixed with -), or filtered.

| Feature | Client Request (GET /api/category?...) | Prisma Action | | :--- | :--- | :--- | | Selection | ?fields={id,name} | select: { id: true, name: true } | | Exclusion | ?fields={name,-description} | select: { name: true, description: false } | | Exclusion (Only) | ?fields={-name,-id} | Selects all except name and id. |

2.2. Filtering (fields with {...})

Filters are applied to fields using curly braces {}. Multiple filters on a single field are separated by commas.

A. Basic Filter Syntax

The general syntax is: field{operatorType==operatorValue}

| Example | Request | Meaning | | :--- | :--- | :--- | | Equality | ?fields=id{==Number==100} | where: { id: { equals: 100 } } | | Greater Than | ?fields=price{>Number>50.50} | where: { price: { gt: 50.50 } } | | Inequality | ?fields=name{!=String!=Test} | where: { name: { not: "Test" } } | | Date Range | ?fields=createdAt{>=Date>=2025-10-01,<=Date<=2026-10-01}|

B. Advanced Boolean Filtering (New Feature) 💡

The filter value can now be a complex boolean expression using _and_ and _or_ operators. Nested logic is grouped by pipes |...|.

| Logic | Client Request | Meaning | | :--- | :--- | :--- | | Top-Level OR | ?fields=status{==String=="Active"_or_"Pending"} | where: { status: { in: ["Active", "Pending"] } } (Prisma optimization) | | Nested Logic | ?fields=name{==String==\|'T1'_and_'T2'\|_or_\|'T3'_and_'T4'\|} |

2.3. Relation Inclusion (include)

Relations are included using the include query parameter. Relations can also be filtered, following the same syntax as top-level fields.

| Feature | Client Request | Prisma Action | | :--- | :--- | :--- | | Simple Include | ?include=updatedByAdmin | include: { updatedByAdmin: true } | | Include with Select | ?include=updatedByAdmin(email,firstName) | include: { updatedByAdmin: { select: { email: true, firstName: true } } } | | Include with Filter | ?include=updatedByAdmin(email{==String=="[email protected]"}) | include: { updatedByAdmin: { where: { email: { equals: "[email protected]" } } } } |

2.4. Pagination/Capping (limit)

Limits the number of results returned by the query.

| Feature | Client Request | Prisma Action | | :--- | :--- | :--- | | Limit | ?limit=10 | take: 10 |


3. Security and Access Control

The framework provides robust mechanisms to control what data users can access and request.

3.1. @RestqlGuard 🛡️ (Conditional Access Control)

This decorator applies a standard NestJS Guard only if the client requests a specified field or relation. This is essential for protecting sensitive fields like password hashes or internal audit information.

Usage Example:

TypeScript

@Get()
@RestqlGuard(['include:updatedByAdmin'], JwtAuthGuard) // The JwtAuthGuard will ONLY run if the client requests ?include=updatedByAdmin
find(@Query() query: RestqlQueryDto) { /* ... */ }

| Guarded Field | Request (/api/category?...) | Result | | :--- | :--- | :--- | | include:updatedByAdmin | ?fields=name | SUCCESS. JwtAuthGuard is skipped. | | include:updatedByAdmin | ?include=updatedByAdmin(id) | FAILURE if JwtAuthGuard fails (user not logged in). | | direct:secretAuditField | ?fields=secretAuditField | Guard runs. |

3.2. @RestqlIgnore and Global Ignore 🚫 (Data Hiding)

This mechanism ensures that specific fields are always excluded from the final query result, regardless of what the client requests. This is primarily used for security (preventing exposure of fields) or to remove fields that should never be queried.

A. Global Ignore (App Module)

Fields defined here are removed from every request.

TypeScript

// main.ts
const globalRestqlIgnore = ['updatedByAdmin:password']; // Removes password from the 'updatedByAdmin' relation

app.useGlobalInterceptors(new RestqlIgnoreInterceptor(reflector, globalRestqlIgnore));

B. Route-Level Ignore (@RestqlIgnore)

Fields defined here are removed only for the specific controller method.

TypeScript

@Get('public')
@RestqlIgnore('direct:internalNote', 'include:adminLog') // Removes top-level 'internalNote' and the entire 'adminLog' relation
publicFind(@Query() query: RestqlQueryDto) { /* ... */ }

| Ignore Syntax | Target | Action | | :--- | :--- | :--- | | fieldName or direct:fieldName | Top-level model | Explicitly excludes fieldName from the result set. | | relationName:fieldName | Included relation | Explicitly excludes fieldName from the relationName's included object. | | include:relationName | Included relation | Removes the entire relationName inclusion from the query. |

Next.js Adapter for easier query construction

https://www.npmjs.com/package/restql-client-next