Drizzle Tables
Routes take and return the models of your Drizzle ORM tables like any other type. The RunTypes Drizzle ORM section covers declaring tables, column formats, views and migrations.
Using Models in Routes
A route typed with your table models is validated before the handler runs, refined bounds included. Dates stay Dates on server and client, the client gets full types, and you register nothing per route. Both table forms give the same models, so only the import differs.
import {RpcError} from '@mionjs/core';
import {createMionRouter} from '@mionjs/router';
import type {NewUser, User, UserPatch} from './drizzle-refine-example.ts';
const mion = createMionRouter();
const usersStore = new Map<string, User>();
export const usersApi = mion.initRoutes({
users: {
// id and createdAt are optional (table defaults), so the handler fills them
insert: mion.route((_ctx, user: NewUser): User => {
const row: User = {
id: user.id ?? crypto.randomUUID(),
name: user.name,
age: user.age,
createdAt: user.createdAt ?? new Date(),
};
usersStore.set(row.id, row);
return row;
}),
// createdAt arrives on the client as a real Date
select: mion.route(
(_ctx, id: string): User | RpcError<'user-not-found'> => {
return (
usersStore.get(id) ??
new RpcError({
publicMessage: 'User not found',
type: 'user-not-found',
})
);
}
),
// any subset is accepted, but a too-short name is still rejected
update: mion.route(
(
_ctx,
id: string,
patch: UserPatch
): User | RpcError<'user-not-found'> => {
const existing = usersStore.get(id);
if (!existing)
return new RpcError({
publicMessage: 'User not found',
type: 'user-not-found',
});
const next: User = {...existing, ...patch};
usersStore.set(id, next);
return next;
}
),
},
});
// the client's types come from this
export type UsersApi = typeof usersApi;
import {RpcError} from '@mionjs/core';
import {createMionRouter} from '@mionjs/router';
import type {NewUser, User, UserPatch} from './drizzle-types-refine-example.ts';
const mion = createMionRouter();
const usersStore = new Map<string, User>();
export const usersApi = mion.initRoutes({
users: {
// id and createdAt are optional (table defaults), so the handler fills them
insert: mion.route((_ctx, user: NewUser): User => {
const row: User = {
id: user.id ?? crypto.randomUUID(),
name: user.name,
age: user.age,
createdAt: user.createdAt ?? new Date(),
};
usersStore.set(row.id, row);
return row;
}),
// createdAt arrives on the client as a real Date
select: mion.route(
(_ctx, id: string): User | RpcError<'user-not-found'> => {
return (
usersStore.get(id) ??
new RpcError({
publicMessage: 'User not found',
type: 'user-not-found',
})
);
}
),
// any subset is accepted, but a too-short name is still rejected
update: mion.route(
(
_ctx,
id: string,
patch: UserPatch
): User | RpcError<'user-not-found'> => {
const existing = usersStore.get(id);
if (!existing)
return new RpcError({
publicMessage: 'User not found',
type: 'user-not-found',
});
const next: User = {...existing, ...patch};
usersStore.set(id, next);
return next;
}
),
},
});
// the client's types come from this
export type UsersApi = typeof usersApi;
Returning Query Results
A row you query from a toDrizzle table has the model's type, formats included. A route can return it as is, and the client checks the same lengths, ranges and ids.
import {eq} from 'drizzle-orm';
import {drizzle} from 'drizzle-orm/pg-proxy';
import {createMionRouter} from '@mionjs/router';
import {toDrizzle} from '@mionjs/drizzle-orm-pg-core/drizzle';
import {schema} from './drizzle-relations-example.ts';
import {
authors,
posts,
type Author,
type Post,
type AuthorWithPosts,
} from './drizzle-relations-schema-example.ts';
const authorsDb = toDrizzle(authors);
const postsDb = toDrizzle(posts);
const db = drizzle(async () => ({rows: []}), {schema});
const mion = createMionRouter();
export const blogApi = mion.initRoutes({
// whole rows
listAuthors: mion.route(
async (): Promise<Author[]> => db.select().from(authorsDb)
),
// selected model fields
authorNames: mion.route(
async (): Promise<Pick<Author, 'id' | 'name'>[]> =>
db.select({id: authorsDb.id, name: authorsDb.name}).from(authorsDb)
),
// one model per joined table
postsWithAuthor: mion.route(
async (): Promise<{post: Post; author: Author}[]> =>
db
.select({post: postsDb, author: authorsDb})
.from(postsDb)
.innerJoin(authorsDb, eq(postsDb.authorId, authorsDb.id))
),
// nested models
authorsWithPosts: mion.route(
async (): Promise<AuthorWithPosts[]> =>
db.query.authors.findMany({with: {posts: true}})
),
});
Keeping Drizzle Types Out of Routes
Route parameters and return types use slim models, never Drizzle types, so the client never type-checks or bundles your database code. The linter warns when a route uses a Drizzle type.