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

@rabstack/rab-query

v0.6.0

Published

Type-safe, bidirectional query string parser for building REST APIs with rich filtering capabilities

Readme

RabQuery

Type-safe, bidirectional query string parser for building REST APIs with rich filtering capabilities.

Parse query strings into typed objects (Server) and encode objects into query strings (Client) with operator syntax inspired by Prisma, supporting nested objects and logical conditions.

Features

  • Bidirectional: Client → Query String ↔ Server Object
  • Type-Safe: Full TypeScript support with generics
  • Rich Operators: 19+ operators with syntax inspired by Prisma
  • Nested Objects: Deep filtering with dot notation (user.profile.age)
  • Logical Operators: OR, AND, NOT conditions
  • Sorting: Nested orderBy support
  • Zero Dependencies: Lightweight and fast
  • Framework Agnostic: Works with any Node.js framework or database

Installation

npm install @rabstack/rab-query
# or
yarn add @rabstack/rab-query
# or
pnpm add @rabstack/rab-query

Quick Start

Client Side (Build Query Strings)

import { encodeRabQuery } from '@rabstack/rab-query';

// Build a query string from an object
const queryString = encodeRabQuery({
  name: { contains: 'John' },
  age: { gte: 18 },
  status: { in: ['active', 'pending'] },
  orderBy: [{ createdAt: 'desc' }]
});

// Result: "name__contains=John&age__gte=18&status__in=active,pending&orderBy=createdAt__desc"

Server Side (Parse Query Strings)

import { buildQueryParameters } from '@rabstack/rab-query';

// Express.js example
app.get('/users', (req, res) => {
  const params = buildQueryParameters(req.query);

  // Use with Prisma
  const users = await prisma.user.findMany({
    where: params,
    orderBy: params.orderBy
  });

  res.json(users);
});

Note: rab-query focuses on filtering and sorting. Pagination (page, pageSize) is intentionally not included to keep the library generic - implement pagination according to your specific needs.

API Reference

Core Functions

buildQueryParameters<T>(filters: Record<string, any>): RabQueryParams<T>

Parses URL query parameters into a typed object suitable for database queries.

Server-side usage: Parse incoming request query strings.

// URL: ?name__contains=john&age__gte=18
// Express automatically parses to: req.query = { name__contains: 'john', age__gte: '18' }

const params = buildQueryParameters(req.query);

// Result:
{
  name: { contains: 'john', mode: 'insensitive' },
  age: { gte: 18 }
}

encodeRabQuery<T>(query: RabQueryParams<T>): string

Encodes a typed object into a URL query string.

Client-side usage: Build query strings for API requests.

const queryString = encodeRabQuery({
  name: { contains: 'John' },
  age: { gte: 18 }
});

// Output: "name__contains=John&age__gte=18"

Supported Operators

Operator syntax inspired by Prisma:

Comparison Operators

  • equals - Exact match
  • not - Not equal
  • in - Match any value in array
  • notIn - Match none of the values in array
  • lt - Less than
  • lte - Less than or equal
  • gt - Greater than
  • gte - Greater than or equal

String Operators

  • contains - Contains substring (case-insensitive)
  • startsWith - Starts with substring
  • endsWith - Ends with substring
  • search - Full-text search

Array/List Operators

  • has - Array contains value
  • hasSome - Array contains some values
  • hasEvery - Array contains all values
  • isEmpty - Array is empty

Null Checks

  • isSet - Field is set (not null)

Usage Examples

Basic Filtering

// Simple equality
const params = buildQueryParameters({ name: 'John' });
// → { name: 'John' }

// With operator
const params = buildQueryParameters({ 'age__gte': '18' });
// → { age: { gte: 18 } }

Nested Objects

Use dot notation for deep filtering:

// Client side
const queryString = encodeRabQuery({
  user: {
    profile: {
      age: { gte: 18 }
    }
  }
});
// → "user.profile.age__gte=18"

// Server side
const params = buildQueryParameters({ 'user.profile.age__gte': '18' });
// → { user: { profile: { age: { gte: 18 } } } }

Array Operators

// IN operator (comma-separated values)
const params = buildQueryParameters({ 'status__in': 'active,pending' });
// → { status: { in: ['active', 'pending'] } }

// Array contains
const params = buildQueryParameters({ 'tags__hasSome': 'javascript,typescript' });
// → { tags: { hasSome: ['javascript', 'typescript'] } }

Logical Operators (OR, AND, NOT)

// OR condition - append __or to any operator
const params = buildQueryParameters({
  'age__gt__or': '18',
  'age__lt__or': '65'
});
// → { OR: [{ age: { gt: 18 } }, { age: { lt: 65 } }] }

// Client side - build OR queries
const queryString = encodeRabQuery({
  OR: [
    { age: { gt: 18 } },
    { status: 'premium' }
  ]
});
// → "age__gt__or=18&status__or=premium"

Sorting (OrderBy)

// Single field
const params = buildQueryParameters({ 'orderBy': 'createdAt__desc' });
// → { orderBy: [{ createdAt: 'desc' }] }

// Multiple fields (comma-separated)
const params = buildQueryParameters({ 'orderBy': 'name__asc,createdAt__desc' });
// → { orderBy: [{ name: 'asc' }, { createdAt: 'desc' }] }

// Nested fields
const params = buildQueryParameters({ 'orderBy': 'user.profile.age__desc' });
// → { orderBy: [{ user: { profile: { age: 'desc' } } }] }

// Client side - array format (multiple fields)
const queryString = encodeRabQuery({
  orderBy: [
    { name: 'asc' },
    { createdAt: 'desc' }
  ]
});
// → "orderBy=name__asc,createdAt__desc"

// Client side - single object format (one field)
const queryString = encodeRabQuery({
  orderBy: { name: 'asc' }
});
// → "orderBy=name__asc"

// Client side - single object with multiple fields (NEW!)
const queryString = encodeRabQuery({
  orderBy: { name: 'asc', createdAt: 'desc' }
});
// → "orderBy=name__asc,createdAt__desc"

// All three formats work!
// Note: For critical sort priority, array format is recommended for explicit ordering

Complete Example

// Client builds complex query
const queryString = encodeRabQuery({
  name: { contains: 'John' },
  age: { gte: 18, lte: 65 },
  status: { in: ['active', 'pending'] },
  user: {
    profile: {
      verified: true
    }
  },
  OR: [
    { role: 'admin' },
    { credits: { gt: 100 } }
  ],
  orderBy: [{ createdAt: 'desc' }]
});

// Server parses and uses with database
app.get('/users', async (req, res) => {
  const params = buildQueryParameters(req.query);

  // Implement your own pagination
  const page = Number(req.query.page) || 1;
  const pageSize = Math.min(Number(req.query.pageSize) || 10, 100);

  const users = await prisma.user.findMany({
    where: params,
    take: pageSize,
    skip: (page - 1) * pageSize,
    orderBy: params.orderBy
  });

  res.json(users);
});

TypeScript Support

RabQuery is fully typed with generics for your data models:

interface User {
  id: string;
  name: string;
  age: number;
  email: string;
  profile: {
    bio: string;
    verified: boolean;
  };
}

// Type-safe query building
const params: RabQueryParams<User> = buildQueryParameters(req.query);

// Type-safe encoding
const queryString = encodeRabQuery<User>({
  name: { contains: 'John' },
  age: { gte: 18 },
  profile: {
    verified: true
  }
});

// Autocomplete and type checking!

Core Types

import type {
  RabQueryParams,      // Main query type
  BaseRabQuery,        // Base query type with orderBy
  RabQueryFilters,     // Filter types
  OrderByInput,        // OrderBy type
  FieldOperators,      // Available operators per field
  SupportedOperator,   // All operator names
  SortOrder,           // 'asc' | 'desc'
} from '@rabstack/rab-query';

Query String Format

RabQuery uses a specific format for query strings:

Basic Format

field__operator=value

Nested Objects (dot notation)

user.profile.age__gte=18

Conditions (suffix)

age__gt__or=18
status__equals__and=active

Arrays (comma-separated)

status__in=active,pending,approved
tags__hasSome=javascript,typescript

Multiple Values (same key)

?age__gt__or=18&age__lt__or=65
// Results in: OR: [{ age: { gt: 18 } }, { age: { lt: 65 } }]

Advanced Usage

Utility Functions

import {
  parseQueryKey,
  isSupportedOperator,
  isArrayOperator,
  isCaseInsensitiveOperator,
  supportedOperators
} from '@rabstack/rab-query';

// Parse individual query keys
const parsed = parseQueryKey('user.age__gte__or');
// → { path: ['user', 'age'], modifier: { operator: 'gte', condition: 'or' } }

// Check operator types
isSupportedOperator('contains'); // true
isArrayOperator('in'); // true
isCaseInsensitiveOperator('contains'); // true

Custom Validation

import { buildQueryParameters, RabQueryParams } from '@rabstack/rab-query';

function validateAndParse<T>(query: any): RabQueryParams<T> {
  const params = buildQueryParameters<T>(query);

  // Add custom validation as needed
  // Example: Validate specific fields or business rules
  if (params.age && typeof params.age === 'object' && 'gte' in params.age) {
    if (params.age.gte < 0) {
      throw new Error('Age cannot be negative');
    }
  }

  return params;
}

Framework Integration

Express.js

import express from 'express';
import { buildQueryParameters } from '@rabstack/rab-query';

const app = express();

app.get('/api/users', async (req, res) => {
  const params = buildQueryParameters(req.query);
  const users = await db.users.find(params);
  res.json(users);
});

NestJS

import { Controller, Get, Query } from '@nestjs/common';
import { buildQueryParameters } from '@rabstack/rab-query';

@Controller('users')
export class UsersController {
  @Get()
  async findAll(@Query() query: any) {
    const params = buildQueryParameters(query);
    return this.usersService.findAll(params);
  }
}

Fastify

import Fastify from 'fastify';
import { buildQueryParameters } from '@rabstack/rab-query';

const fastify = Fastify();

fastify.get('/users', async (request, reply) => {
  const params = buildQueryParameters(request.query);
  return db.users.find(params);
});

React Query (Client)

import { useQuery } from '@tanstack/react-query';
import { encodeRabQuery } from '@rabstack/rab-query';

function useUsers(filters: RabQueryParams<User>) {
  return useQuery({
    queryKey: ['users', filters],
    queryFn: async () => {
      const queryString = encodeRabQuery(filters);
      const response = await fetch(`/api/users?${queryString}`);
      return response.json();
    }
  });
}

// Usage
const { data } = useUsers({
  name: { contains: 'John' },
  age: { gte: 18 }
});

Best Practices

  1. Always validate input: RabQuery parses query strings but doesn't validate business rules
  2. Implement pagination separately: Add page and pageSize handling according to your needs
  3. Use TypeScript: Leverage generic types for better type safety
  4. Sanitize user input: While RabQuery is safe from injection, always validate data
  5. Document your API: Share the query format with your API consumers

Roadmap

Future Enhancements

  • Elasticsearch Adapter: Transform RabQuery output to Elasticsearch Query DSL
    • Convert Prisma-style operators to Elasticsearch format
    • Map containsmatch, gte/lterange, interms
    • Transform OR/AND/NOT to bool queries (should/must/must_not)
    • Maintain type safety and full query compatibility

Migration from V1

RabQuery V2 is backward compatible with V1. The main differences:

  • New nested object support via dot notation
  • Extended operator set (19 vs 8 operators)
  • Improved TypeScript types
  • New encodeRabQuery function for client-side

Existing V1 queries will continue to work without changes.

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT