Type-safe repository layer for ORMs.
Computed fields, derived fields, Prisma-like query DSL, and framework integrations.
| Package | Description |
|---|---|
| @relayerjs/drizzle | Drizzle ORM adapter — main package |
| @relayerjs/core | ORM-agnostic types and contracts |
| @relayerjs/next | Next.js App Router CRUD integration |
| @relayerjs/nestjs-crud | NestJS CRUD controllers with DI, hooks, Swagger |
| @relayerjs/nestjs-graphql | NestJS GraphQL code-first CRUD with filtering and pagination |
- Why Relayer?
- Features
- Quick Start
- Next.js Integration
- NestJS Integration
- NestJS GraphQL Integration
- Documentation
- Examples
- Roadmap
- Contributing
- License
Relayer is a repository layer that sits between your ORM and your API. It makes dynamic fields (computed, derived) a first-class part of the data model with full support for filtering, sorting, and aggregation — not through raw SQL escape hatches, but as a core design principle. The query DSL is a plain JSON-serializable object, making it trivial to wire up as REST or GraphQL filters.
Currently ships with a Drizzle ORM adapter and framework integrations for Next.js and NestJS (REST, GraphQL). The project is in active development — adapters for Kysely, TypeORM, and other ORMs are planned.
- First-class dynamic fields — computed, derived, and JSON fields are type-safe, filterable, sortable, and selectable — treated equally to regular columns
- Complex filtering — AND, OR, NOT, relation filters (some, every, none), custom SQL in where, 20+ operators
- Relations — batch loading without N+1, per-relation row limits (
$limit), connect/disconnect/set for managing relations in mutations - Aggregations — _count, _sum, _avg, _min, _max with groupBy and having — full support for computed, derived, and JSON fields
- Type-safe autocomplete — full inference for own fields, nested relations, and even nested derived fields across entities
- Prisma-like query DSL — findMany, findFirst, where, select, orderBy — JSON-serializable, ready for REST/GraphQL
- Transactions —
$transactionwith automatic wrapping for relation operations - Typed context — pass per-request data (user, tenant) to field resolvers for row-level logic
- Multi-dialect — PostgreSQL, MySQL, SQLite with dialect-aware optimizations
- Type-safe App Router route handlers (GET, POST, PATCH, DELETE)
- Built-in validation (Zod) and lifecycle hooks
- SSR direct calls — same Relayer client, no HTTP roundtrip
- Configurable field whitelists, operator restrictions, and pagination limits
- Full-featured REST CRUD — services, controllers, and route generation out of the box
- GraphQL code-first CRUD — one
@GqlResolverdecorator generates queries, mutations, and all GraphQL types - Lifecycle hooks and DTO mapping for full control over request/response pipeline
- Complex filters — AND, OR, relations, JSON fields, computed/derived fields, search
- Cursor and offset pagination — both strategies, or both at once
- Swagger/OpenAPI auto-documentation for REST
npm install @relayerjs/drizzle drizzle-ormimport { createRelayerDrizzle, createRelayerEntity } from '@relayerjs/drizzle';
import { db } from './db';
import * as schema from './schema'; // your Drizzle schema
const UserEntity = createRelayerEntity(schema, 'users');
class User extends UserEntity {
@UserEntity.computed({
resolve: ({ table, sql }) => sql`${table.firstName} || ' ' || ${table.lastName}`,
})
fullName!: string;
@UserEntity.derived({
query: ({ db, schema: s, sql, field }) =>
db
.select({ [field()]: sql`count(*)::int`, userId: s.posts.authorId })
.from(s.posts)
.groupBy(s.posts.authorId),
on: ({ parent, derived, eq }) => eq(parent.id, derived.userId),
})
postsCount!: number;
}
const r = createRelayerDrizzle({
db,
schema,
entities: { users: User },
});const users = await r.users.findMany({
select: { id: true, fullName: true, postsCount: true },
where: { email: { contains: '@example.com' } },
orderBy: { field: 'postsCount', order: 'desc' },
limit: 10,
});
// JSON filtering
const admins = await r.users.findMany({
where: { metadata: { role: 'admin', level: { gte: 5 } } },
});
// Relations with per-relation row limit
const usersWithPosts = await r.users.findMany({
select: { id: true, fullName: true, posts: { $limit: 5, title: true } },
});
// Aggregations
const stats = await r.orders.aggregate({
groupBy: ['status'],
_count: true,
_sum: { total: true },
});For the full API reference (mutations, transactions, relations, type utilities), see the @relayerjs/drizzle README.
@relayerjs/next turns your Relayer entities into type-safe App Router route handlers with validation, hooks, and SSR support.
npm install @relayerjs/next @relayerjs/core @relayerjs/drizzle drizzle-orm next// lib/routes.ts
import { createRelayerRoute } from '@relayerjs/next';
export const userRoutes = createRelayerRoute(r, 'users', {
allowWhere: { email: { operators: ['eq', 'contains'] } },
allowOrderBy: ['name', 'createdAt', 'postsCount'],
maxLimit: 100,
});
// app/api/users/route.ts
export const GET = userRoutes.list({
defaultSelect: { id: true, name: true, postsCount: true },
defaultOrderBy: { field: 'createdAt', order: 'desc' },
});
export const POST = userRoutes.create();
// app/api/users/[id]/route.ts
export const { GET, PATCH, DELETE } = userRoutes.detailHandlers();Full documentation: @relayerjs/next README
@relayerjs/nestjs-crud provides DI-native services and auto-generated CRUD controllers with lifecycle hooks, DTO mapping, Swagger, and cursor/offset pagination.
npm install @relayerjs/nestjs-crud @relayerjs/core @relayerjs/drizzle drizzle-orm@Injectable()
export class PostsService extends RelayerService<PostEntity, EM> {
constructor(@InjectRelayer() r: RelayerInstance<EM>) {
super(r, PostEntity);
}
}
@CrudController<PostEntity, EM>({
model: PostEntity,
routes: {
list: {
defaults: { orderBy: { field: 'createdAt', order: 'desc' } },
maxLimit: 50,
defaultLimit: 20,
},
create: { schema: createPostSchema },
update: { schema: updatePostSchema },
},
})
export class PostsController extends RelayerController<PostEntity, EM> {
constructor(postsService: PostsService) {
super(postsService);
}
}Auto-generated routes:
| Method | Path | Description |
|---|---|---|
GET |
/posts |
List with pagination, filtering, sorting, search |
GET |
/posts/:id |
Find by ID |
POST |
/posts |
Create (validated) |
PATCH |
/posts/:id |
Update (validated) |
DELETE |
/posts/:id |
Delete |
GET |
/posts/count |
Count matching records |
GET |
/posts/aggregate |
Aggregation with groupBy |
POST |
/posts/:id/relations/:name |
Connect relation |
DELETE |
/posts/:id/relations/:name |
Disconnect relation |
Full documentation: @relayerjs/nestjs-crud README
@relayerjs/nestjs-graphql generates a complete code-first GraphQL API from your Relayer entities. One decorator, full CRUD.
npm install @relayerjs/nestjs-graphql @relayerjs/core @relayerjs/drizzle @relayerjs/nestjs-common@GqlResolver(UserEntity, { name: 'User' })
export class UsersResolver extends RelayerResolver<UserEntity, EM> {
constructor(usersService: UsersService) {
super(usersService);
}
}Auto-generated operations:
| Type | Name | Description |
|---|---|---|
| Query | users |
Cursor-paginated list (Connection) |
| Query | user(id: ID!) |
Find by ID |
| Query | usersCount(where: ...) |
Count matching records |
| Query | usersAggregate(...) |
Aggregation with groupBy |
| Mutation | createUser(data: ...) |
Create one |
| Mutation | updateUser(id: ID!, ...) |
Update one |
| Mutation | deleteUser(id: ID!) |
Delete one |
All GraphQL types, filter inputs, and sorting are generated automatically. Supports cursor and offset pagination, relation filters, lifecycle hooks, and typed context.
Full documentation: @relayerjs/nestjs-graphql README
Full documentation is available at relayerjs.vercel.app
| Topic | Link |
|---|---|
| Drizzle adapter | packages/drizzle/README.md |
| Next.js integration | packages/next/README.md |
| NestJS CRUD | packages/nestjs-crud/README.md |
| NestJS GraphQL | packages/nestjs-graphql/README.md |
| Example | Directory |
|---|---|
| Drizzle (PostgreSQL, MySQL, SQLite) | examples/drizzle |
| NestJS CRUD | examples/nestjs-crud |
| NestJS GraphQL | examples/nestjs-graphql |
| Next.js App Router | examples/next |
Relayer is in early development. Planned packages:
- @relayerjs/rest: auto-generate REST CRUD endpoints (Express, Fastify)
- @relayerjs/react: React client with hooks for querying Relayer endpoints
Contributions are always welcome.
- Node.js >= 20
- pnpm >= 10
- Docker (for PostgreSQL and MySQL integration tests)
git clone https://github.com/awHamer/relayer.git
cd relayer
pnpm install
pnpm buildRun examples
cd examples
docker compose up -d # start PostgreSQL + MySQL
cd drizzle
pnpm seed # create tables + seed data
pnpm start # run PG example
npx tsx src/test-mysql.ts # run MySQL example
npx tsx src/test-sqlite.ts # run SQLite exampleRun tests
pnpm -r test # all packages (requires Docker for integration tests)
pnpm --filter @relayerjs/drizzle test:unit # drizzle unit tests only (no DB)
pnpm --filter @relayerjs/drizzle test:pg # drizzle PostgreSQL integration
pnpm --filter @relayerjs/drizzle test:mysql # drizzle MySQL integration
pnpm --filter @relayerjs/drizzle test:sqlite # drizzle SQLite integration (in-memory)
pnpm --filter @relayerjs/nestjs-crud test # nestjs-crud
pnpm --filter @relayerjs/nestjs-graphql test # nestjs-graphql
pnpm --filter @relayerjs/next test # nextRun docs locally
pnpm docs:dev # start dev server at localhost:4321
pnpm docs:build # production build