Type-safe repository layer for ORMs with computed fields, derived fields, and a powerful query DSL. Currently supports Drizzle ORM.
Over the years, across many projects, we kept reimplementing the same pattern: a repository layer that brings database queries closer to API filters and makes dynamic fields (computed, derived) a first-class part of the data model, with full support for filtering, sorting, and aggregation. Not as an afterthought, not through raw SQL escape hatches, but as a core design principle.
Relayer is that pattern extracted into a library. Built with API integration in mind from day one:
- Class-based entity model with decorator-driven computed and derived fields
- API-friendly query DSL:
findMany,where,select,orderBywith 20+ operators. The DSL is a plain JSON-serializable object, making it trivial to wire up as REST/GraphQL filters - Computed fields: virtual SQL expressions, no raw queries
- Derived fields: automatic subquery JOINs with full filtering and sorting support
- First-class JSON filtering: nested path queries with full comparison operators
- Typed context: pass per-request data (current user, tenant, etc.) to field resolvers
- Class-based entities: define computed and derived fields with decorators on entity classes
- Query DSL: select, where, orderBy, limit, offset
- Computed fields: virtual columns defined as SQL expressions
- Derived fields: automatic subquery JOINs (scalar and object types)
- First-class JSON integration: transparent nested filtering with auto type casting
- 20+ filter operators: eq, ne, gt, gte, lt, lte, in, contains, ilike, isNull, and more
- Array operators: arrayContains, arrayContained, arrayOverlaps (PostgreSQL)
- Relation filters: exists, some, every, none
- Nested relation fields: computed and derived fields on relations with cross-entity type propagation
- Aggregations: _count, _sum, _avg, _min, _max with groupBy and dot-notation
- Typed context: pass per-query context to computed/derived resolvers
- Transactions: $transaction with automatic client scoping
- Multi-dialect: PostgreSQL, MySQL, SQLite
- Full TypeScript inference: select, where, orderBy, and result types
Currently only Drizzle ORM (>=0.38.0) is supported. Future plans include adapters for TypeORM, Kysely, MikroORM, and others. The goal is a single unified query interface regardless of the underlying ORM. Contributions are always welcome.
npm install @relayerjs/drizzle drizzle-ormimport { relations } from 'drizzle-orm';
import { integer, jsonb, pgTable, serial, text } from 'drizzle-orm/pg-core';
const users = pgTable('users', {
id: serial('id').primaryKey(),
firstName: text('first_name').notNull(),
lastName: text('last_name').notNull(),
email: text('email').notNull(),
metadata: jsonb('metadata').$type<{ role: string; level: number }>(),
});
const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
published: boolean('published').default(false).notNull(),
authorId: integer('author_id')
.notNull()
.references(() => users.id),
});
const usersRelations = relations(users, ({ many }) => ({
posts: many(posts),
}));
const postsRelations = relations(posts, ({ one }) => ({
author: one(users, { fields: [posts.authorId], references: [users.id] }),
}));
const schema = { users, posts, usersRelations, postsRelations };import { createRelayerDrizzle, createRelayerEntity } from '@relayerjs/drizzle';
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, // your drizzle instance
schema,
entities: { users: User },
});// Select + filter + order
const results = await r.users.findMany({
select: { id: true, firstName: true, fullName: true, postsCount: true },
where: { email: { contains: '@example.com' } },
orderBy: { field: 'firstName', order: 'asc' },
limit: 10,
});
// JSON filtering: transparent nested queries
const admins = await r.users.findMany({
where: { metadata: { role: 'admin', level: { gte: 5 } } },
});
// Load relations
const usersWithPosts = await r.users.findMany({
select: { id: true, firstName: true, posts: { title: true } },
});
// Relation filters
const activeAuthors = await r.users.findMany({
where: { posts: { some: { published: true } } },
});
// Aggregations: all functions + groupBy + dot-notation joins
const stats = await r.orders.aggregate({
groupBy: ['status'],
_count: true,
_sum: { total: true },
_avg: { total: true },
});
// Group by relation field (auto LEFT JOIN)
const ordersByUser = await r.orders.aggregate({
groupBy: ['user.firstName'],
_count: true,
_sum: { total: true },
});| Package | Description |
|---|---|
| @relayerjs/drizzle | Drizzle ORM adapter, main package |
| @relayerjs/core | ORM-agnostic types and contracts |
| @relayerjs/next | Next.js App Router integration |
Full documentation is available at relayerjs.vercel.app
See also the Drizzle adapter README for a quick API reference.
See the examples/drizzle directory for runnable examples with PostgreSQL, MySQL, and SQLite.
Relayer is in early development. Planned packages:
- @relayerjs/rest: auto-generate REST CRUD endpoints (Express, Fastify, Hono)
- @relayerjs/nest: NestJS module with CRUD controllers and GraphQL resolvers
- @relayerjs/graphql: standalone GraphQL schema generation
- @relayerjs/react: React client with hooks for querying Relayer endpoints
- 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 buildcd 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 examplepnpm --filter @relayerjs/drizzle test # all tests
pnpm --filter @relayerjs/drizzle test:unit # unit tests only (no DB)
pnpm --filter @relayerjs/drizzle test:pg # PostgreSQL integration
pnpm --filter @relayerjs/drizzle test:mysql # MySQL integration
pnpm --filter @relayerjs/drizzle test:sqlite # SQLite integration (in-memory)pnpm docs:dev # start dev server at localhost:4321
pnpm docs:build # production buildRelayer is inspired by Prisma query API, Hasura GraphQL filters, nestjs-query, and many other tools that make database access feel effortless.