Quick Start
This walkthrough takes a query from the client, over the wire, through validation, into a database query.
Shared types for both sides:
type Realm = {
id: string,
name: string,
};
type User = {
id: number,
name: string,
email: string,
age: number,
realm: Realm,
};1. Build a query (client)
Construct a Query from AST nodes and encode it as a URL query string:
import {
Field, Fields,
Filter, FilterCompoundOperator, FilterFieldOperator, Filters,
Pagination, Query, Relation, Relations, Sort, Sorts,
} from '@rapiq/core';
import { URLEncoder } from '@rapiq/codec-url-simple';
const query = new Query({
fields: new Fields([new Field('id'), new Field('name')]),
filters: new Filters(FilterCompoundOperator.AND, [
new Filter(FilterFieldOperator.GREATER_THAN_EQUAL, 'age', 18),
]),
relations: new Relations([new Relation('realm')]),
sorts: new Sorts([new Sort('age', 'DESC')]),
pagination: new Pagination(25, 0),
});
const encoder = new URLEncoder();
const queryString = encoder.encode(query);
// fields=id,name&filter[age]=>=18&page[limit]=25&page[offset]=0&include=realm&sort=-age
const response = await fetch(`/users?${queryString}`);2. Parse & validate (server)
Declare a Schema — the allow-list of what a client may request — and parse the incoming input against it:
import { SchemaRegistry, defineSchema } from '@rapiq/core';
import { SimpleParser } from '@rapiq/parser-simple';
const registry = new SchemaRegistry();
registry.add(defineSchema<Realm>({
name: 'realm',
fields: { allowed: ['id', 'name'] },
}));
registry.add(defineSchema<User>({
name: 'user',
fields: { allowed: ['id', 'name', 'email', 'age'] },
filters: { allowed: ['id', 'name', 'age'] },
relations: { allowed: ['realm'] },
sort: { allowed: ['id', 'name', 'age'] },
pagination: { maxLimit: 50 },
schemaMapping: { realm: 'realm' },
}));
const parser = new SimpleParser(registry);
// express parses the query string into an object for you (req.query)
const query = parser.parse(req.query, { schema: 'user' });Anything outside the allow-lists is silently dropped; set throwOnFailure: true on the schema to get a ParseError instead. See Schemas.
Raw query strings
If you only have the raw string, parse it with qs and feed the result to SimpleParser — or use the schema-less URLDecoder when validation isn't needed.
3. Apply to the database (server)
With TypeORM, the adapter mutates a SelectQueryBuilder in place:
import { QueryVisitor } from '@rapiq/sql';
import { TypeormAdapter } from '@rapiq/typeorm';
const queryBuilder = dataSource.getRepository(User).createQueryBuilder('user');
const adapter = new TypeormAdapter({
relations: { joinAndSelect: true },
});
adapter.withQuery(queryBuilder);
query.accept(new QueryVisitor(adapter));
adapter.execute();
const [entities, total] = await queryBuilder.getManyAndCount();Without TypeORM, @rapiq/sql renders parameterized SQL fragments you can feed to any driver.
Going further
- Concepts — the Query AST, schemas and the visitor pattern.
- Filters — every filter operator and its syntax.
- Integrations — parsers, codecs and backend adapters in detail.