Skip to content

One schema in.
Everything else out.

DRZL is a code generator for Drizzle ORM. Point it at the schema you already wrote and it emits the validation schemas, API routers and typed services to match. Open any row below to read the file it writes.

src/schema.ts

export const users =
  pgTable('users', {
    id:    integer(),
    email: varchar(),
    age:   integer(),
    tier:  text(),
  });
ZodusersInsert = z.object({ id, email, age, ... })view file

src/gen/users.zod.ts

import { z } from 'zod';

export const usersInsertSchema = z.object({
  id:    z.number().int(),
  email: z.string().max(255),
  age:   z.number().int().gte(18),
  tier:  z.string(),
});
// plus usersSelectSchema, same shape
Valibotv.object({ id: v.pipe(v.number(), v.integer()) ... })view file

src/gen/users.valibot.ts

import * as v from 'valibot';

export const usersInsertSchema = v.object({
  id:    v.pipe(v.number(), v.integer()),
  email: v.pipe(v.string(), v.maxLength(255)),
  age:   v.pipe(v.number(), v.minValue(18)),
});
JSON Schema{ "type": "object", "required": ["email", ...] }view file

src/gen/users.schema.json

{
  "type": "object",
  "required": ["email", "age"],
  "properties": {
    "email": { "type": "string", "maxLength": 255 },
    "age":   { "type": "integer", "minimum": 18 }
  }
}
tRPCusersRouter.create.input(usersInsert)view file

src/api/users.router.ts

import { usersInsertSchema } from '../gen';

export const usersRouter = router({
  create: publicProcedure
    .input(usersInsertSchema)
    .mutation(({ input }) => UsersService.create(input)),
});
Honoapp.post('/users', sValidator('json', usersInsert))view file

src/api/users.routes.ts

app.post('/users',
  sValidator('json', usersInsertSchema),
  async (c) => c.json(
    await UsersService.create(c.req.valid('json')), 201));
NestJSclass CreateUserDto { ... }view file

src/api/dto/create-user.dto.ts

export class CreateUserDto {
  @IsString() @MaxLength(255) email: string;
  @IsInt() @Min(18) age: number;
}
GraphQLtype User { id: Int! email: String! ... }view file

src/api/schema.graphql

type User {
  id: Int!
  email: String!
  age: Int!
}
input CreateUserInput { email: String! age: Int! }
ServicesUsersService.create(input) to db.insert(users)view file

src/services/users.service.ts

export class UsersService {
  static async create(input: UsersInsert) {
    return db.insert(users).values(input).returning();
  }
}
  • 27 generators
  • 1 install
  • 0 runtime dependencies added
  • Node, Bun and Deno
  • Apache-2.0

Works with

Everything here is a generator DRZL ships, a provider with a quickstart, or a runtime the output is measured on. Each name links to the page that backs it.

Validation

Six generator kinds, plus the OpenAPI document the JSON Schema generator writes.

Routers and servers

One generator kind each, emitting the router or the app that framework expects, except react-hook-form and TanStack Form, which are the two targets of the one forms kind.

Databases

The dialects shown here. The analyzer reads seven; the first three below are asked a real server on every commit, and the providers under them have quickstarts.

Runtimes

The emitted code and the CLI were run on all three, and the generated tree is byte-identical across them.

React Hook Form and TanStack Form are the two targets of one forms kind rather than a generator each. Every schema DRZL emits in zod, valibot or arktype spelling carries Standard Schema v1, which both accept directly, so the resolver is one line either way; what the generator adds is the per-field metadata a control needs, which is the half you would otherwise write by hand.

Product names and marks belong to their owners, and appear here only to say which software DRZL generates code for. No endorsement or affiliation is implied by any of them.

Constraints, and what they cost

DRZL also reads the CHECK constraints declared on your table, which most generators skip, so a row that passes the generated schema is a row the database will accept. On the eight-column table the benchmark uses that is four of the four constraints, against none. Each one the first-party schema misses is a row that passes validation and then fails at the database, which is the worst place to find out. Rejecting a bad row costs about the same either way, within a few percent, and that is the path an API actually spends its validation time in. Accepting a good row costs DRZL 15% to 21% more, depending on the run, and that is the price of the four extra checks.

The throughput figures, the generated file size and the machine they were measured on stay on one page rather than being repeated here: a second copy of a measurement is a copy that goes stale quietly. The constraint count is not a measurement of a machine, and the same question is put to a real Postgres, a real SQLite and a real MySQL on every commit, which is how it is verified.

Funded Features

  • None yet. Be the first! Need a template, generator, or adapter that doesn’t exist yet? DM me on X (https://x.com/omardulaimidev) to fund it. All funded work ships back into DRZL under Apache‑2.0.

Need a custom template or integration? DM @omardulaimidev on X.