What is rapiq?
Rapiq (Rest Api Query) gives the two sides of an HTTP API one shared query language for list endpoints: filtering, sorting, pagination, sparse field selection and relation loading, modeled after the JSON:API query parameters.
The caller builds a typed query, sends it as an ordinary URL query string, and the receiving application validates it against an allow-list schema before turning it into a database query. No hand-rolled req.query parsing, no string concatenation, no guessing which parameters a client may touch.
?codec=url-expression&filter=gte(age,'18')&include=realm&sort=-age&page[limit]=25&fields=id,nameThe 30-second tour
Caller: build a query against your record type and encode it:
import { defineQuery } from '@rapiq/core';
import { createURLCodec } from '@rapiq/codec-url';
const query = defineQuery<User>({
filters: { age: { $gte: 18 } },
relations: ['realm'],
sort: '-age',
pagination: { limit: 25 },
});
const response = await fetch(`/users?${createURLCodec().encode(query)}`);Receiver: decode it against a schema that says what clients may request, then hand it to your database:
import { SchemaRegistry, defineSchema } from '@rapiq/core';
import { createURLCodec } from '@rapiq/codec-url';
import { TypeormAdapter } from '@rapiq/adapter-typeorm';
const registry = new SchemaRegistry();
registry.add(defineSchema<User>({
name: 'user',
filters: { allowed: ['age', 'name'] },
relations: { allowed: ['realm'] },
sort: { allowed: ['age', 'name'] },
pagination: { maxLimit: 50 },
}));
const query = createURLCodec(registry).decode(req.query, { schema: 'user' });
if (!query) {
// null for non-object input
return res.status(400).end();
}
new TypeormAdapter({ queryBuilder }).execute(query);
const [entities, total] = await queryBuilder.getManyAndCount();Everything a client sends is constrained before it reaches the database. Most parameters follow the schema's drop-vs-throw policy; expression filters reject contract violations precisely.
How it works
A query passes through four stages, and every rapiq package plays exactly one role in one of them:
defineQuery<User>({ filters, sort, … })typed input · condition helpers eq / and / orURLCodec?codec=url-expression&filter=gte(age,'18')&include=realm&sort=-age&page[limit]=25URLCodec / parsersaccept(visitor)- Build: the caller describes what it wants with
defineQuery, typed against the record. - Send: a codec turns the query into an ordinary URL query string, and back.
- Validate: the receiver parses against a schema; the allow-list decides what survives.
- Execute: an adapter translates the validated query for your backend.
Because the pieces only meet in the Query, they compose freely: swap the wire dialect without touching the database code, add a new backend without touching the parsers, or skip the wire entirely and evaluate a query in memory.
The five parameters
| Parameter | URL key | What it does |
|---|---|---|
| Fields | fields | Select which resource fields are returned. |
| Filters | filter | Narrow the collection by conditions. |
| Relations | include | Load related resources alongside the primary one. |
| Sort | sort | Order the collection by one or more keys. |
| Pagination | page | Limit and offset the collection. |
The package family
Install only what each side of your application needs; @rapiq/core is the shared foundation.
| Role | Packages |
|---|---|
| Build & compose | @rapiq/core: the query AST, defineQuery, condition helpers, schemas |
| Parse input | @rapiq/parser-simple · @rapiq/parser-expression · @rapiq/parser-mongo |
| Cross the wire | @rapiq/codec-url |
| Execute | @rapiq/adapter-typeorm · @rapiq/adapter-sql · @rapiq/adapter-prisma · @rapiq/adapter-drizzle · @rapiq/adapter-memory |
See the package overview for the full map and a "which packages do I need?" guide.
When is rapiq a good fit?
- Your API exposes list endpoints and clients need to filter, sort, paginate or shape the result.
- You want the query surface declared, typed and enforced instead of scattered across handlers.
- You want the same query semantics everywhere: SQL, TypeORM, Prisma, Drizzle, in-memory guards, tests.
It deliberately does not define the response format, replace your ORM, or generate endpoints; it only standardizes what a query is and what a client may ask for.
Version 2
These docs cover the upcoming version 2, which splits the former single rapiq package into focused @rapiq/* packages. The v1 documentation lives on the v1 branch; see Migration from v1.
Next steps
- Installation: add the packages for your side of the wire.
- Quick Start: caller to database in one walkthrough.
- Core Concepts: the four moving parts, explained once.