Quick Start
Install Turbine, point it at your database, generate a typed client, and run your first query.
Requirements#
- Node.js ≥ 20 (the package
enginesfield). The optional SQLite engine uses Node's built-innode:sqliteand needs Node ≥ 22.5. - PostgreSQL 14+ for the default path (Neon, Vercel Postgres, Supabase, or local all work). Other engines are documented under Database Engines.
"type": "module"in your projectpackage.jsonis recommended. Turbine is an ESM-first package, but the CLI also works in CommonJS projects (the default fromnpm init -y): it loadsturbine.config.tsand schema files correctly either way.tsxas a dev dependency so the CLI can load TypeScript config and schema files (turbine.config.ts,turbine/schema.ts, TypeScript seeds).
1. Install#
npm install turbine-orm
npm install --save-dev tsxTurbine has exactly one runtime dependency (pg). ESM and CommonJS both work for importing the library; the CLI still needs tsx for .ts project files.
If your package.json does not already say so, set:
{
"type": "module"
}2. Set DATABASE_URL#
Set the standard Postgres connection string in your environment. For local development, use a .env file:
# .env
DATABASE_URL=postgres://user:pass@localhost:5432/mydb3. Initialize the project#
npx turbine initThis creates turbine.config.ts at the project root and a turbine/ directory for your schema and migrations. The config file looks like this:
// turbine.config.ts
import type { TurbineCliConfig } from 'turbine-orm/cli';
const config: TurbineCliConfig = {
url: process.env.DATABASE_URL,
out: './generated/turbine',
schema: 'public',
migrationsDir: './turbine/migrations',
schemaFile: './turbine/schema.ts',
};
export default config;
schemavsschemaFile:
schemais the Postgres schema name (the namespace, almost always'public').schemaFileis the path to yourdefineSchema()TypeScript file (used byturbine pushand auto-diff migrations).Never put a file path in
schema. A value like'./turbine/schema.ts'makes introspection look forWHERE table_schema = './turbine/schema.ts', which matches zero tables. Put the path inschemaFileonly.
4. Generate the typed client#
After init, pick the path that matches your database.
Path A, Existing tables#
If the database already has tables, introspect them and emit a fully-typed client:
npx turbine generate
# or: npx turbine pullPath B, Empty database#
If the database is empty (or you want to own the schema in TypeScript first), define tables, push them, then generate:
- Edit
turbine/schema.tswithdefineSchema(...), see Schema & Migrations. - Apply it to the database:
npx turbine push- Generate the typed client from the live schema:
npx turbine generatepush is the fast local path. For production, prefer SQL migrations (turbine migrate) instead of push, details on the schema page.
What gets written#
Either path writes three files to ./generated/turbine/ (or your config out):
types.ts, entity interfaces andCreate/Updateinput typesmetadata.ts, runtime schema metadataindex.ts, a typedTurbineClientsubclass with table accessors
5. Your first query#
import { turbine } from './generated/turbine';
// No arguments needed: turbine() reads DATABASE_URL from the environment.
// (Pass { connectionString } explicitly if you'd rather not rely on the env var.)
const db = turbine();
const users = await db.users.findMany({
where: { role: 'admin' },
orderBy: { createdAt: 'desc' },
limit: 10,
});
console.log(users);
await db.disconnect();Loading
.envin your app: theturbineCLI auto-loads a local.env, but your application does not (the library never reads files). Populateprocess.env.DATABASE_URLthe standard way, e.g. run the script with Node's built-in flag:node --env-file=.env app.js(Node 20.12+), or callprocess.loadEnvFile()at startup. Any framework that already loads.env(Next.js, etc.) works too.
db.users is typed, autocompletion surfaces every column, every operator, every relation. users[0].createdAt is a Date, users[0].email is a string, everything is inferred from the generated types.
6. Add nested relations#
const usersWithPosts = await db.users.findMany({
where: { orgId: 1 },
with: {
posts: {
with: { comments: { with: { author: true } } },
orderBy: { createdAt: 'desc' },
limit: 5,
},
},
});
// One statement, one round-trip, fully typed all the way down:
usersWithPosts[0].posts[0].comments[0].author;Turbine compiles the with clause to a single SQL statement using PostgreSQL's json_agg and correlated subqueries. No client-side stitching, no N+1. The default 'auto' strategy keeps that plan unless it can prove the join would be the slower one for a particular relation (an unindexed correlation column, say), in which case it moves that one relation to a follow-up statement and returns identical rows. Pin relationLoadStrategy: 'join' for the single statement unconditionally, see Load strategies.
Next steps#
Migrating an existing app? Start with one of these rather than the API reference:
- Migrate from Prisma,
turbine migrate-from-prismareads yourschema.prismaand emits a typed mapping, andcreatePrismaCompatClientgives you aPrismaClient-shaped surface so your existinginclude/take/cursorcall sites keep working while you port. - Migrate from Drizzle, the API mapping, the schema translation, and the behavioural differences worth auditing before you cut over.
- Why Turbine, if you are still deciding: what is genuinely different, and what is table stakes in 2026.
Otherwise, keep going:
- Vector Search, typed pgvector KNN ranking and distance filters, no raw SQL.
- Realtime, Postgres LISTEN/NOTIFY pub/sub with
$listen/$notify. - Transactions & RLS Sessions, SAVEPOINTs, isolation levels, and
sessionContextfor Row-Level Security multi-tenancy. - Studio, a local, read-only database UI:
npx turbine studio. - Observability, query lifecycle events and built-in metrics aggregation.
- API Reference, every query method, WHERE operator, and nested
withoption. - Schema & Migrations, define your schema in TypeScript and ship migrations.
- Database Engines, SQLite, MySQL, SQL Server, and PowDB behind the same typed API.
- CLI, every command with examples.
- Benchmarks: honest numbers against Prisma 7 and Drizzle 0.45 on a local PostgreSQL 17 database.