How to build Drizzle explicit types

Call me dumb, but I don't fully get how we can have explicit types that we can easily import in the various components of the application. I've worked few months with Prisma, and over there you have the engine that's able to generate all the types that your queries will generate and you can import them as needed. As far as I can see, Drizzle ORM does not do that. While I feel that what it does is still great, they provide you the type definition with all the props properly typed but you don't have a way to import them. You rely on the implicit types that the query will expose. Don't you think that's kind of limiting your ability to have explicit types that you can import in your components? A quick example Let's say you have a component that display the values from the following query:
export async function getRoleBySlug(slug: string) {
return await db.query.roles.findFirst({
where: (role, { eq }) => eq(role.slug, slug),
});
}
export async function getRoleBySlug(slug: string) {
return await db.query.roles.findFirst({
where: (role, { eq }) => eq(role.slug, slug),
});
}
If I hover on the function name with VS Code I get the following message:
function getRoleBySlug(slug: string): Promise<{
id: number;
name: string;
slug: string;
} | undefined>
function getRoleBySlug(slug: string): Promise<{
id: number;
name: string;
slug: string;
} | undefined>
While this is semantically correct, how do you type the prop inside the component that will consume the result from this query? Are you doing something like this?
type Role = {
id: number;
name: string;
slug: string;
}

export default function DisplayRole({ role }: { role: Role }) {
// Your component code...
}
type Role = {
id: number;
name: string;
slug: string;
}

export default function DisplayRole({ role }: { role: Role }) {
// Your component code...
}
I mean, doesn't it feel "too much"? An approach with drizzle-zod I've started to use drizzle-zod since I already use zod inside my application, and now in each of my schemas I add the following:
import { createInsertSchema, createSelectSchema } from 'drizzle-zod';

const roles = pgTable('roles', {
id: serial('id').primaryKey(),
slug: varchar('slug', { length: 255 }).unique().notNull(),
name: varchar('name', { length: 255 }).notNull(),
});

export const rolesInsertSchema = createInsertSchema(roles);
export const rolesSelectSchema = createSelectSchema(roles);

export default roles;
import { createInsertSchema, createSelectSchema } from 'drizzle-zod';

const roles = pgTable('roles', {
id: serial('id').primaryKey(),
slug: varchar('slug', { length: 255 }).unique().notNull(),
name: varchar('name', { length: 255 }).notNull(),
});

export const rolesInsertSchema = createInsertSchema(roles);
export const rolesSelectSchema = createSelectSchema(roles);

export default roles;
And then I find myself generating the types inside my index.d.ts file like so:
import type { roleSelectSchema } from '@/src/drizzle/schema/roles';

export type Role = z.infer<typeof roleSelectSchema>;
import type { roleSelectSchema } from '@/src/drizzle/schema/roles';

export type Role = z.infer<typeof roleSelectSchema>;
So now I can go back to the previous <DisplayRole /> component and import the Role type. Do you have a better approach to make TypeScript fully working? Is probably my project not configured well enough to work with Drizzle?
2 Replies
Jean
Jean2mo ago
Instead of drizzle-zod, you can also use the built-in type API in drizzle-orm: https://orm.drizzle.team/docs/goodies
const roles = pgTable('roles', {
id: serial('id').primaryKey(),
slug: varchar('slug', { length: 255 }).unique().notNull(),
name: varchar('name', { length: 255 }).notNull(),
});

export type RolesInsert = roles.$inferInsert;
export type RolesSelect = roles.$inferSelect;
const roles = pgTable('roles', {
id: serial('id').primaryKey(),
slug: varchar('slug', { length: 255 }).unique().notNull(),
name: varchar('name', { length: 255 }).notNull(),
});

export type RolesInsert = roles.$inferInsert;
export type RolesSelect = roles.$inferSelect;
Then explicitly define the return type with the RolesSelect type:
export async function getRoleBySlug(slug: string): Promise<RolesSelect | undefined> {
return await db.query.roles.findFirst({
where: (role, { eq }) => eq(role.slug, slug),
});
}
export async function getRoleBySlug(slug: string): Promise<RolesSelect | undefined> {
return await db.query.roles.findFirst({
where: (role, { eq }) => eq(role.slug, slug),
});
}
When you pass the result to a prop, check for undefined. Either handle it in the parent component before you pass the prop or within your component. You can then type the prop using the RolesSelect type as well.
import type { RolesSelect } from "...";

export default function DisplayRole({ role }: { role: RolesSelect }) {
// Your component code...
}
import type { RolesSelect } from "...";

export default function DisplayRole({ role }: { role: RolesSelect }) {
// Your component code...
}
or:
import type { RolesSelect } from "...";

export default function DisplayRole({ role }: { role: RolesSelect | undefined }) {
if (!role) {
// Something to show when the role doesn't exist.
}
// Your component code...
}
import type { RolesSelect } from "...";

export default function DisplayRole({ role }: { role: RolesSelect | undefined }) {
if (!role) {
// Something to show when the role doesn't exist.
}
// Your component code...
}
Let me know if this helps!
Drizzle ORM - Goodies
Drizzle ORM is a lightweight and performant TypeScript ORM with developer experience in mind.
cupofcrypto
cupofcrypto4w ago
Thanks for your help, on mobile now so just wanted to thank you as soon as I saw this. I'll get back to you once I'll be working with a keyboard 😅