# Drizzle vs Prisma for TanStack Start: Which ORM for Your SaaS?

> Drizzle vs Prisma in a TanStack Start SaaS: how each ORM handles Nitro cold starts, bundle size, the build step, migrations, types in server functions, and raw SQL. Code from both MakerKit kits and the one we default to.

*Published: 2026-07-02*
*Canonical: https://makerkit.dev/blog/tutorials/tanstack-start-drizzle-vs-prisma*

---

For a TanStack Start SaaS, use Drizzle unless you have a specific reason to pick Prisma. Drizzle has a small bundle, no client-generation step and no native binary, and its types update as soon as you save the schema. Choose Prisma if your team prefers a schema-first `.prisma` file, wants Prisma Studio, or is porting an existing Prisma codebase. Both ORMs work with TanStack Start, and the MakerKit TanStack Start kit ships in both variants, with Drizzle as the default.

The choice differs from a plain Node server in two ways. TanStack Start builds through Vite and Nitro, so the same app can deploy to a Node server, to serverless functions or to edge runtimes. And your database code runs inside `createServerFn` handlers rather than route handlers.

This post covers only what changes when the framework is TanStack Start. For schema syntax, query APIs and performance in general, read the [Drizzle vs Prisma comparison](/blog/tutorials/drizzle-vs-prisma). If you are new to the framework, start with [what is TanStack Start](/blog/tutorials/what-is-tanstack-start); if you are still choosing a framework, see [TanStack Start vs Next.js](/blog/tutorials/tanstack-start-vs-nextjs).

**TL;DR**

- **Both ORMs work with TanStack Start.** In MakerKit they live in `packages/database`, and nothing else in the kit changes between variants.
- **Drizzle** is about [7.4 KB min+gzip with zero dependencies](https://github.com/drizzle-team/drizzle-orm), has no generate step and no binary, and infers types directly from your TypeScript schema.
- **Prisma 7** removed the Rust query engine, so it now deploys to the same serverless and edge targets. It still has a larger client and requires `prisma generate`.
- **Pick Drizzle** for SQL-shaped queries, the smallest cold-start cost and no build step. **Pick Prisma** for schema-first modeling, Prisma Studio, or an existing Prisma codebase.
- **MakerKit's TanStack Start kit defaults to Drizzle.** The Prisma variant differs only in `packages/database`.

## Drizzle vs Prisma in TanStack Start: the comparison table

The table lists the differences that matter once TanStack Start is the framework.

| Dimension in a TanStack Start kit | Drizzle | Prisma 7 |
|-----------------------------------|---------|----------|
| Fit with Nitro serverless/edge targets | Native. No binary, tiny bundle | Works since v7 (Rust removed); larger client |
| Cold start impact | Minimal | Improved in v7, still heavier |
| Build step before `vite build` | None | `prisma generate` required |
| Type inference in server functions | Instant on save | After `generate` |
| Client shape | Query builder + schema objects | Generated client in your source tree |
| Raw SQL inside a server fn | `sql` template tag, native | `$queryRaw` / `$executeRaw` |
| Where it lives in the kit | `packages/database` | `packages/database` (Prisma variant) |
| What else changes | Nothing | Nothing |

In the MakerKit kits, everything outside the ORM is identical between the two variants: Better Auth, billing, the organization and teams model, and role-based access control. Only `packages/database` differs. That makes the ORM choice reversible and much cheaper to get wrong than the framework choice.

## Where the ORM lives in a TanStack Start kit

Both kits export a single `db` client from `@kit/database`, and server functions import it from there. The Drizzle kit's package entry point is short:

```typescript
// packages/database/src/index.ts — TanStack Start Drizzle kit
export { db, type DatabaseSchema } from './client.server';
export * from './schema/schema';

// export auth context utilities
export * from './auth';
```

The client is a `postgres-js` connection wrapped by Drizzle. The `prepare: false` option is required for transaction-mode connection pooling, the same setting you need with Supabase's pooler:

```typescript
// packages/database/src/adapters/postgres.ts — Drizzle kit
import { PostgresJsDatabase, drizzle } from 'drizzle-orm/postgres-js';
import postgres from 'postgres';

import { databaseUrl } from '../database-url.server';
import * as schema from '../schema/schema';

export type DatabaseSchema = typeof schema;

function createPostgres() {
  // Disable prefetch as it's not supported for "Transaction" pool mode
  return postgres(databaseUrl, {
    prepare: false,
    max: Number(process.env.DATABASE_POOL_MAX) || 20,
  });
}
```

The Prisma variant replaces this file with a `PrismaClient` built on the `@prisma/adapter-pg` driver adapter over a `pg` pool:

```typescript
// packages/database/src/adapters/postgres.ts — TanStack Start Prisma kit
import { PrismaPg } from '@prisma/adapter-pg';
import { Pool } from 'pg';

import { databaseUrl } from '../database-url';
import { PrismaClient } from '../prisma/generated/client';

function createPrismaClient() {
  const pool = new Pool({ connectionString: databaseUrl });
  const adapter = new PrismaPg(pool);

  return new PrismaClient({ adapter });
}
```

In Prisma 7 the generated client is written to your source tree (`src/prisma/generated/client`), not to `node_modules`. This affects the build order, covered below.

## Which ORM has smaller cold starts on Nitro serverless and edge?

Drizzle. It loads less code per cold invocation, and the gap only matters on serverless and edge targets.

TanStack Start builds through Nitro, and you choose the deploy target: a long-running Node server, a serverless function (Vercel, AWS Lambda) or an edge runtime. On a long-running Node server, cold starts are rare and either ORM is fine. On serverless and edge, the runtime loads and initializes your bundle on every cold invocation, so the ORM's size adds directly to cold-start latency.

Drizzle is about [7.4 KB min+gzip with zero dependencies](https://github.com/drizzle-team/drizzle-orm) and has no native binary. At runtime it adds little beyond your own code and the Postgres driver. This is the main reason MakerKit uses it as the default for TanStack Start.

[Prisma 7 removed the Rust query engine](https://www.prisma.io/blog/prisma-7), so the old rule that Prisma can't run on the edge no longer applies. The client is pure TypeScript and deploys to serverless and edge targets that earlier versions could not run on. The client is still much larger than Drizzle's, so each cold start initializes more code. On a warm Node server you won't notice. If you are trying to keep serverless cold starts low, you will.

In short, Prisma 7 can now run everywhere Drizzle can, but it still ships more code. TanStack Start is built around fast Vite builds and multiple deploy targets, and the smaller client fits that better.

## Does Prisma need a build step in TanStack Start?

Yes. Prisma needs `prisma generate` before your Vite build and before typecheck. Drizzle has no client to generate: the schema is TypeScript, so the types are current as soon as you save `schema.ts`.

If `prisma generate` doesn't run first, the generated client is stale or missing. The Next.js Prisma kit runs it from the package's `build` script:

```json
// packages/database/package.json — Prisma kit
"scripts": {
  "build": "prisma generate",
  "prisma:generate": "prisma generate",
  "prisma:migrate": "prisma migrate dev"
}
```

The Drizzle kit has no client-generation script. Its scripts only handle migrations and Drizzle Studio:

```json
// packages/database/package.json — Drizzle kit
"scripts": {
  "drizzle:generate": "drizzle-kit generate --config drizzle.config.mjs",
  "drizzle:migrate": "drizzle-kit migrate --config drizzle.config.mjs",
  "drizzle:studio": "drizzle-kit studio --config drizzle.config.mjs"
}
```

The names overlap: `drizzle-kit generate` writes migration SQL files. It does not generate a client.

The generate step is a common source of "why are my types wrong?" questions on the Prisma side. The usual cause is an edited schema without a regenerate. Each regeneration takes seconds, but you repeat it after every schema change.

Prisma 8 is in release candidate as of September 2026, with general availability expected in October 2026 according to [Prisma's release status page](https://www.prisma.io/docs/orm/release-status). It replaces client generation with a lighter "contract emit" step, so a build step remains. The MakerKit Prisma kits use Prisma 7.

## Migrations workflow

Both ORMs generate SQL migration files, and in both kits migrations run from `packages/database`, independent of the app framework.

Drizzle uses `drizzle-kit`. The kit uses timestamp-prefixed migration filenames so kit migrations and your own never share a sequence number:

```javascript
// packages/database/drizzle.config.mjs — Drizzle kit
export default defineConfig({
  schema: './src/schema/schema.ts',
  out: './src/schema',
  dialect: 'postgresql',
  migrations: { prefix: 'timestamp' },
  schemaFilter: ['public'],
  verbose: true,
  strict: true,
});
```

With `strict: true`, `drizzle-kit` asks you to confirm ambiguous changes such as a column rename instead of treating them as a drop plus an add, which would lose the column's data. Keep it on.

Prisma uses `prisma migrate dev`, configured in `prisma.config.ts`:

```typescript
// packages/database/prisma.config.ts — Prisma kit
export default {
  schema: path.join('src', 'prisma', 'schema.prisma'),
  migrations: { path: path.join('src', 'prisma', 'migrations') },
  datasource: {
    url: process.env.DATABASE_URL || 'postgresql://postgres:postgres@127.0.0.1:54333/postgres',
  },
} satisfies PrismaConfig;
```

Neither workflow depends on TanStack Start. Because migrations live in `packages/database`, you can change and test the schema without starting the web app.

## Type safety inside TanStack Start server functions

Both ORMs give you full type inference inside a `createServerFn` handler or the service it calls. The difference is when the types update: Drizzle's on save, Prisma's after `prisma generate`.

Here is a service from the Drizzle kit that an admin server function calls. It runs two counts in parallel:

```typescript
// packages/admin/.../admin-dashboard.service.ts — Drizzle kit
import { count, eq } from 'drizzle-orm';
import { db, user } from '@kit/database';

async getAdminStats() {
  const [totalUsersResult, bannedUsersResult] = await Promise.all([
    db.select({ count: count() }).from(user),
    db.select({ count: count() }).from(user).where(eq(user.banned, true)),
  ]);

  return {
    totalUsers: totalUsersResult[0]?.count ?? 0,
    bannedUsers: bannedUsersResult[0]?.count ?? 0,
  };
}
```

The server function contains no query code. It delegates to the loader, which calls the service:

```typescript
// apps/web/src/lib/admin.functions.ts — Drizzle kit
import { createServerFn } from '@tanstack/react-start';
import { loadAdminStats } from '@kit/admin/dashboard/loader';

export const fetchAdminStats = createServerFn({ method: 'GET' }).handler(() =>
  loadAdminStats(),
);
```

In the Prisma variant the server function is the same. Only the service changes: `db.user.count()` and `db.user.count({ where: { banned: true } })` replace the Drizzle `select` calls.

## Raw SQL access

Drizzle's `sql` template tag works inside the query builder, so you can mix raw SQL into a typed query. Prisma's `$queryRaw` and `$executeRaw` are separate methods on the client.

Most SaaS apps eventually need a query the ORM API doesn't cover, such as a window function, a recursive CTE or a hand-tuned aggregate. With Drizzle, you add the raw expression to an otherwise typed query:

```typescript
import { eq, sql } from 'drizzle-orm';

const rows = await db
  .select({
    org: organization,
    memberCount: sql<number>`count(distinct ${member.userId})`,
  })
  .from(organization)
  .leftJoin(member, eq(member.organizationId, organization.id))
  .groupBy(organization.id);
```

With Prisma, you write the whole query in `$queryRaw` and type the result yourself. If your codebase uses raw SQL often, Drizzle keeps it in the same API as your other queries. If you rarely write raw SQL, this difference shouldn't decide anything.

## When to choose Drizzle for TanStack Start

- You deploy to **Nitro serverless or edge targets** and want the lowest cold-start cost.
- You want **no build step**: no client generation, types current on save.
- You are **comfortable with SQL** and want queries that map directly to it.
- You write **raw SQL** often and want it inside typed queries.
- You want **MakerKit's default TanStack Start setup** with the least configuration.

## When to choose Prisma for TanStack Start

- You prefer **schema-first modeling** in a `.prisma` file over TypeScript schema objects.
- You want **Prisma Studio** for browsing data during development.
- You are **porting an existing Prisma codebase** and don't want to rewrite every query.
- Your team prefers Prisma's **object-based query API** to SQL-shaped queries.

If you already test Prisma code, our guide to [unit testing Prisma with pgLite and Vitest](/blog/tutorials/unit-testing-prisma-vitest) applies to the Prisma variant.

## What MakerKit defaults to

The MakerKit TanStack Start kit uses Drizzle by default. TanStack Start is a Vite + Nitro framework built around typed server functions, and Drizzle's lack of a generate step, lack of a native binary and small bundle fit that model with the least setup, especially on serverless and edge targets. The code samples above come from that kit.

The kit also ships a Prisma variant. As with our Next.js Drizzle and Prisma kits, the two differ only in `packages/database`; Better Auth, billing, teams and RBAC are shared. For how auth is wired in both, see [Better Auth with TanStack Start](/blog/tutorials/better-auth-tanstack-start).

Switching later is possible. Because the ORM sits behind `@kit/database` and is only called from services, switching means rewriting `packages/database` and updating the query calls in your services. Routes, server functions and UI stay the same.

To get started, read the [TanStack Start Drizzle kit docs](/docs/tanstack-drizzle/installation/overview) or the [TanStack Start Prisma kit docs](/docs/tanstack-prisma/installation/overview).

{% faq
   title="Frequently Asked Questions"
   items=[
     {"question": "Which ORM should I use with TanStack Start?", "answer": "Default to Drizzle. It is about 7.4 KB min+gzip, has no code-generation step and no native binary, which fits TanStack Start's Vite + Nitro build and its server-function data model. Choose Prisma if you want schema-first modeling, Prisma Studio, or you are porting existing Prisma code. Both work in production."},
     {"question": "Does Prisma work with TanStack Start?", "answer": "Yes. Prisma 7 removed the Rust query engine, so the client is pure TypeScript and deploys to the serverless and edge targets TanStack Start builds for via Nitro. It has a larger client than Drizzle and requires running 'prisma generate' before the build."},
     {"question": "Does Drizzle work on the edge with TanStack Start?", "answer": "Yes. Drizzle has no native binary and a small bundle, so it runs on Nitro's edge and serverless targets without special configuration. Pair it with a Postgres driver or connection pooler that supports your deploy target."},
     {"question": "Do I need to run a build step for the ORM in TanStack Start?", "answer": "With Drizzle, no. Your schema is TypeScript and types are current on save; 'drizzle-kit generate' only writes migration SQL. With Prisma, yes: 'prisma generate' must run before your Vite build and typecheck, otherwise the generated client is stale or missing."},
     {"question": "How does the ORM get used inside a TanStack Start server function?", "answer": "Both MakerKit kits export a single 'db' client from '@kit/database'. A createServerFn handler validates input and calls a service that imports 'db' and runs queries. The server-function code is the same in the Drizzle and Prisma variants; only the query syntax inside the service differs."},
     {"question": "Can I switch ORMs later in a MakerKit TanStack Start kit?", "answer": "Yes. The ORM lives behind '@kit/database', so switching means rewriting that package's client and schema and updating the query calls in your services. Better Auth, billing, teams and RBAC are shared and don't change."},
     {"question": "Is there a Prisma version of the MakerKit TanStack Start kit?", "answer": "Yes. The TanStack Start kit comes in Drizzle and Prisma variants that differ only in 'packages/database', the same way our Next.js Drizzle and Prisma kits relate."},
     {"question": "Which ORM has smaller cold starts on TanStack Start?", "answer": "Drizzle. At about 7.4 KB min+gzip with no binary, it initializes less code per cold invocation than Prisma's larger client. On a warm Node server the difference is negligible; on serverless and edge targets it adds to every cold start."}
   ]
/%}
