Pebellslearn by building
0%
RoadmapsMatrixCoursesSandboxSign in
D1 — Serverless SQL  ›  Capstone Project

Drizzle and Prisma — Typed Queries on Top of D1

Drizzle 12 minORMs like Drizzle and Prisma describe D1 tables in code so queries are type-checked, generate migration SQL from the schema, and run over the same D1 binding.

Drizzle and Prisma — Typed Queries on Top of D1

Hoplink's SQL now lives in string literals scattered across a dozen handlers. Rename the url column to target and nothing warns you: the strings still compile, and the bug shows up in production as undefined. An ORM (object-relational mapper) lets you describe tables once in TypeScript, so the compiler knows every column and catches mistakes before deploy.

The rule

Describe tables once in code; let the types guard every query.

Drizzle is a light ORM that feels close to SQL. You declare the schema in TypeScript:

// src/schema.ts
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";

export const links = sqliteTable("links", {
  code: text("code").primaryKey(),
  url: text("url").notNull(),
  owner: text("owner").notNull(),
  clicks: integer("clicks").notNull().default(0),
});

Then you query through the D1 binding:

import { drizzle } from "drizzle-orm/d1";
import { eq, desc } from "drizzle-orm";
import { links } from "./schema";

const db = drizzle(env.DB);
const mine = await db.select().from(links)
  .where(eq(links.owner, "priya"))
  .orderBy(desc(links.clicks))
  .limit(5);

mine is typed: your editor knows it has code, url, owner and clicks, and a typo like links.ownr is a compile error. Values are always bound as parameters, so injection safety comes for free.

The companion tool drizzle-kit compares your schema file with the previous one and writes the migration SQL for you. You still apply it with wrangler d1 migrations apply, so the numbered-files discipline from earlier stays in place.

Prisma takes a different route: you write a schema.prisma file, generate a client, and connect it to D1 through its driver adapter (@prisma/adapter-d1). Its query style is more object-shaped (prisma.link.findMany({ where: { owner: "priya" } })) and it offers similar type safety. Drizzle is smaller and closer to raw SQL; Prisma is more abstract and batteries-included. Both sit on top of the same D1 binding.

The shortcut

Keep a single rule: the schema file is the truth; migrations are generated from it; queries are checked against it. When something changes, change it in the schema first and let the tools carry it outward.

Renaming url to target with DrizzleStep 1 of 4
  1. Edit the schema

    Change url: text("url") to target: text("target") in schema.ts.

Trap: editing the database by hand and forgetting the schema file. The types then describe a database that no longer exists, and the safety net is gone.

Drills

Answer fast — trust the rule.

quick drill

What is the main gain from describing Hoplink's tables in a Drizzle schema?

quick drill

How does Drizzle connect to Hoplink's database inside a Worker?

quick drill

What does drizzle-kit generate produce?

quick drill

Which statement about Prisma on D1 is true?

Remember

  • Describe tables once in code; let the types guard every query.
  • Shortcut: schema first → generated migration → type-checked queries.
  • Trap: hand edits to the database that the schema file never hears about.

Practice — level up

0/1 solved

Write your answer, then compare it with a rubric and a model answer.

ExplainStrings versus a schema

Hoplink renames the url column to target. Compare what happens with raw SQL strings scattered through the code and with a Drizzle schema, and outline the Drizzle workflow for the rename.