@relayerjs/next
v0.1.2
Published
Next.js App Router integration for Relayer - type-safe CRUD route handlers with validation and hooks
Maintainers
Readme
@relayerjs/next
Next.js App Router CRUD integration for Relayer. Type-safe route handlers with hooks, validation, and SSR support.
Installation
npm install @relayerjs/next @relayerjs/drizzle drizzle-orm nextSetup
1. Define your schema and entity models
// db/schema.ts
import { relations } from 'drizzle-orm';
import { integer, pgTable, serial, text } from 'drizzle-orm/pg-core';
export const users = pgTable('users', {
id: serial('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull(),
});
export const posts = pgTable('posts', {
id: serial('id').primaryKey(),
title: text('title').notNull(),
authorId: integer('author_id').references(() => users.id),
});
export const usersRelations = relations(users, ({ many }) => ({ posts: many(posts) }));
export const postsRelations = relations(posts, ({ one }) => ({
author: one(users, { fields: [posts.authorId], references: [users.id] }),
}));Extend models with computed/derived fields if needed:
// db/entities.ts
import { createRelayerEntity } from '@relayerjs/drizzle';
import * as schema from './schema';
const UserEntity = createRelayerEntity(schema, 'users');
const { computed, derived } = UserEntity;
export class User extends UserEntity {
@computed({ resolve: ({ table, sql }) => sql`upper(${table.name})` })
displayName!: string;
@derived({
query: ({ db, schema: s, sql, field }) =>
db
.select({ [field()]: sql`count(*)::int`, authorId: s.posts.authorId })
.from(s.posts)
.groupBy(s.posts.authorId),
on: ({ parent, derived: d, eq }) => eq(parent.id, d.authorId),
})
postsCount!: number;
}
// No computed/derived needed? Just extend without decorators:
export class Post extends createRelayerEntity(schema, 'posts') {}
// Or simply omit this step, Relayer will use Drizzle schemas directly2. Initialize Relayer
// lib/relayer.ts
import { db } from '@/db';
import { Post, User } from '@/db/entities';
import * as schema from '@/db/schema';
import { createRelayerDrizzle } from '@relayerjs/drizzle';
export const r = createRelayerDrizzle({
db,
schema,
entities: { users: User, posts: Post },
defaultRelationLimit: 20, // cap many-type relations at 20 rows
});3. Create routes
// lib/routes.ts
import { createRelayerRoute } from '@relayerjs/next';
import { r } from './relayer';
export const userRoutes = createRelayerRoute(r, 'users', {
allowSelect: { posts: { $limit: 10, title: true } }, // $limit caps relation rows
allowWhere: { email: { operators: ['eq', 'contains'] } },
allowOrderBy: ['name', 'createdAt', 'postsCount'],
maxLimit: 100,
defaultLimit: 20,
});4. Wire up API routes
// app/api/users/route.ts
import { userRoutes } from '@/lib/routes';
export const GET = userRoutes.list({
defaultSelect: { id: true, name: true, email: true, postsCount: true },
defaultOrderBy: { field: 'createdAt', order: 'desc' },
});
export const POST = userRoutes.create({
beforeCreate: async (data, ctx) => {
return { ...data, createdAt: new Date().toISOString() };
},
});// app/api/users/[id]/route.ts
import { userRoutes } from '@/lib/routes';
export const { GET, PATCH, DELETE } = userRoutes.detailHandlers();5. Query with filters, sort, and pagination
GET /api/users?where={"email":{"contains":"@gmail.com"}}&orderBy={"field":"postsCount","order":"desc"}&limit=10
GET /api/users/1
POST /api/users { "name": "John", "email": "[email protected]" }
PATCH /api/users/1 { "name": "Updated" }
DELETE /api/users/1Response:
{
"data": [{ "id": 1, "name": "John", "postsCount": 5 }],
"meta": { "total": 42, "limit": 10, "offset": 0 }
}Hooks
Hooks can intercept any step. Return data to continue, false to cancel, or a Response for full control:
export const POST = userRoutes.create({
beforeCreate: async (data, ctx) => {
if (!ctx.user) return new Response(null, { status: 401 });
return { ...data, createdBy: ctx.user.id };
},
afterCreate: async (created, ctx) => {
await sendWelcomeEmail(created.email);
return created;
},
});SSR Direct Call
Use .query() in Server Components, same config and hooks, no HTTP:
const { data: users, meta } = await listHandler.query({
where: { role: 'admin' },
limit: 10,
});Documentation
Full docs at relayerjs.vercel.app/next/getting-started
