Application patterns

Paginate and use an ORM deliberately

Choose stable pagination and treat Drizzle as a typed query tool rather than a replacement for SQL and migrations.

8 minute lesson

~~~

Any list that grows needs pagination, and the first rule is ordering: order every paginated query by a stable unique sequence. order by created_at alone is not stable when two notes share a timestamp — add the primary key as a tiebreaker, or page boundaries become random.

Offset pagination is easy but can drift as rows change:

select id, title, created_at from notes
where user_id = ?
order by created_at desc, id desc
limit 10 offset 20;

If a new note arrives while a user reads page 2, everything shifts and page 3 repeats an item they already saw. Offsets also get slower as they grow, because the database still walks past every skipped row.

Cursor pagination fits long or changing collections better. Instead of a page number, the client sends the sort values of the last item it saw:

select id, title, created_at from notes
where user_id = ? and (created_at, id) < (?, ?)
order by created_at desc, id desc
limit 10;

The query says “the next 10 older than this exact position.” New rows cannot shift the window, and the index from the earlier lessons serves it at constant cost regardless of depth.

Drizzle is a tool, not a shield

Drizzle can define your schema in TypeScript and give you typed queries with autocomplete:

const rows = await db.select().from(notes)
  .where(eq(notes.userId, userId))
  .orderBy(desc(notes.createdAt), desc(notes.id))
  .limit(10)

That is genuinely useful — no raw string typos, column renames become compile errors. But the deployed database still follows SQL, indexes, constraints, and migration history. The ORM does not add an index you never created, and a Drizzle query producing SCAN notes is exactly as slow as the handwritten version.

Wiring also deserves attention: drizzle-kit generates SQL migration files into a directory, and Wrangler applies them, so verify the migration layout against current D1 and Wrangler configuration — the migrations_dir in wrangler.jsonc must point where drizzle-kit writes. Two tools each half-managing migrations is how schemas drift.

Now add cursor pagination to the notes list, then compare the SQL Drizzle emits with the handwritten version, and run both through EXPLAIN QUERY PLAN to confirm they use the same index.

Lesson completed

Take this course offline

Get every free book and course as PDF and EPUB files.

Get the download library →