Skip to content

rapiq

One query language between client & server. Build JSON:API-style queries on the client, parse them into a typed AST against schema allow-lists on the server — and turn them into SQL or TypeORM queries with composable adapters.

playground

SQL dialect

GET
/users?fields=id,name,age&filter[name]=~to~&filter[age]=%3E%3D21&page[limit]=25&sort=-age
SQL
SELECT "id", "name", "age"
FROM "users"
WHERE ("name" ~* $1 and "age" >= $2)
ORDER BY "age" DESC
LIMIT 25
-- params: ["to",21]

Live — @rapiq/parser-simple, @rapiq/codec-url-simple and @rapiq/sql are running in your browser.

A pipeline, not a parser

🌐

JSON:API-style

Fields, filters, relations, pagination & sort — one consistent query scheme based on the JSON:API specification.

🌲

Typed query AST

Input parses into Query nodes that backends consume via the visitor pattern — new targets never touch core.

🛡️

Schema allow-lists

Declare what clients may request — allowed keys, defaults, mappings. Disallowed input is dropped or throws.

🧩

Pluggable parsers

Parse plain objects or an expression language — both dialects produce the exact same Query AST.

🗄️

SQL & TypeORM

Render parameterized SQL for five dialects, or apply a Query straight to a TypeORM SelectQueryBuilder.

🔒

TypeScript-first

Typed key paths via recursive NestedKeys<T> — allow-lists and defaults autocomplete against your records.

From client to query in three steps

Build on the client, transport as a query string, parse & validate on the server.

# client side — build & encode queries
npm install @rapiq/core @rapiq/codec-url-simple

# server side — parse, validate & translate
npm install @rapiq/core @rapiq/parser-simple @rapiq/sql

@rapiq/typeorm

Straight into TypeORM

Apply a parsed Query directly to a SelectQueryBuilder — the adapter walks the AST and mutates the builder, nothing is stringified twice.

  • TypeormAdapter — wraps any SelectQueryBuilder
  • Visitor-driven — reuses the @rapiq/sql visitors
  • Relations → joins — allowed relations join automatically
  • Parameterized — filter values bind as parameters, never interpolated
Read the TypeORM guide →
controller.ts
import { FiltersVisitor } from '@rapiq/sql';
import { TypeormAdapter } from '@rapiq/typeorm';

const queryBuilder = repository.createQueryBuilder('user');

const adapter = new TypeormAdapter();
adapter.withQuery(queryBuilder);

query.filters.accept(new FiltersVisitor(adapter.filters));
adapter.execute();

const [entities, total] = await queryBuilder.getManyAndCount();

Released under the MIT License.