How to use the ORM in Base

A first-class relational layer built on Drizzle, entities and repositories reached like everything else.

Data
AhraJuly 22, 2026

How to use the ORM in Base

Most frameworks treat the database as something you bolt on. You pick an ORM, you wire it up, you teach it about your app, and you maintain the seam between it and everything else forever. In Base, the ORM is a first-class citizen of the framework. It is configured in the same settings object as your routes and your resolvers, it speaks the same decorator language as the rest of your code, and it ships with a default that already fits the edge. This post covers the what and the why. The concrete code walkthrough follows once the package surface settles.

The mental model

The ORM in Base rests on three ideas. Once you have them, everything else is detail.

Entities are decorated classes. A table in your database is a TypeScript class with decorators that describe its columns and relationships. You do not write SQL schema by hand and a matching model separately. The class is the schema. You describe each column with a decorator, mark the primary key, and declare relationships to other entities as decorated properties. Base reads those decorations and knows your data shape.

Repositories are how you talk to entities. You do not scatter raw queries through your code. For each entity, Base gives you a repository: a typed handle for reading and writing rows of that entity. Your services ask for the repository they need through dependency injection and use it to find, save, and update. The query lives behind a typed method, not in a string.

Connections are keyed and declarative. You declare your database connections in settings under names. There is a default connection, and you can add more named ones beside it. Each connection names its adapter and the entities it manages. A worker that talks to one database has one connection; a worker that talks to several names several. The wiring is data in your settings object, not setup code you run.

Why an ORM as a first-class citizen

When the database layer is part of the framework rather than a library you attached, several things stop being your problem.

The ORM shares the framework's conventions, so it reads like the rest of Base. The same decorator style that defines a GraphQL field defines a column. The same dependency injection that hands a service its collaborators hands it a repository. There is one mental model across your whole worker, not one for the framework and a different one for the data layer.

Your entities become a single source of truth. Because the class carries its own schema in decorators, the description of your data cannot drift away from the code that uses it. There is no separate schema file to keep in sync, because the schema is the class.

And the boundary stays typed. A repository knows what entity it manages, so the rows it returns are typed, the fields you filter on are checked, and a rename in the entity surfaces everywhere it matters at compile time instead of at runtime in production.

The Drizzle default, and why it fits the edge

Base does not make you choose and assemble a query layer. It brings a Drizzle-backed ORM as the default, already fitted to the places Base applications live.

That default is deliberate. Drizzle is a strong fit for Cloudflare Workers: it is lightweight, it is fully typed, and it has adapters for the databases that belong at the edge. Base ships two of those out of the box. Cloudflare D1 gives you SQLite at the edge, close to your users and scaled by the platform. PlanetScale gives you MySQL when you want a managed relational database with horizontal reach. You pick the adapter that matches where your data should live, and the rest of your entity and repository code stays the same.

This is the thesis of Base playing out in the data layer. The framework does not prescribe your architecture, but where it does have an opinion about tools, it gives you a confident, canonical choice so you start from working instead of from a blank file.

Entities, repositories, connections: how they fit

Put the three ideas together and the shape of an ORM-backed worker is clear.

You define your entities as decorated classes, one per table, with their columns and relationships described in place. You declare a connection in your settings, name its adapter, and list the entities it manages. Then, in any service, you ask for the repository of an entity you care about, and Base injects it already pointed at the right connection. Your service reads and writes through that repository in typed methods, and never has to know which adapter is underneath or how the connection was built.

That is the whole loop, and it is the same two moves as everywhere else in Base: add the orm key to your settings naming your connection and entities, and write decorated classes for the entities themselves. The ORM is not a separate world with its own rules. It is the same framework, applied to your data.

What comes next

The concrete walkthrough, defining a real entity, registering a connection, injecting a repository, and running typed reads and writes against both D1 and PlanetScale, follows in the code pass. The ideas above are the part worth holding first: entities as decorated classes, repositories as typed handles, keyed declarative connections, and a Drizzle default chosen for the edge.

How to use the ORM in Base • System