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.
Edit the schema
Change
url: text("url")totarget: text("target")inschema.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.
What is the main gain from describing Hoplink's tables in a Drizzle schema?
How does Drizzle connect to Hoplink's database inside a Worker?
What does drizzle-kit generate produce?
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.
