Quick Start
Install Turbine, connect it to PostgreSQL, generate a typed client and run your first query. It takes about five minutes.
Requirements#
- Node.js 20 or later. The optional SQLite engine needs Node 22.5 or later.
- PostgreSQL 14 or later, local or hosted (Neon, Supabase, Vercel Postgres and others). For other databases, see Database Engines.
1. Install#
npm install turbine-orm
npm install --save-dev tsxpg is Turbine's only runtime dependency. tsx lets the CLI load TypeScript files such as turbine.config.ts and your schema file.
Turbine works from ESM and CommonJS. ESM ("type": "module" in package.json) is recommended.
2. Connect#
Put your connection string in a .env file:
# .env
DATABASE_URL=postgres://user:pass@localhost:5432/mydbThen initialize the project:
npx turbine initinit writes turbine.config.ts, creates turbine/migrations/, and offers to scaffold a schema file and a seed file:
// turbine.config.ts (excerpt)
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;schema is the Postgres schema name, almost always 'public'. schemaFile is the path to your defineSchema() file. Do not put a file path in schema: introspection would find no tables.
3. Generate the typed client#
If your database already has tables, introspect it:
npx turbine generateIf it is empty, describe your tables in turbine/schema.ts:
// turbine/schema.ts
import { defineSchema } from 'turbine-orm';
export default defineSchema({
users: {
id: { type: 'serial', primaryKey: true },
email: { type: 'text', unique: true, notNull: true },
role: { type: 'text', notNull: true, default: "'member'" },
createdAt: { type: 'timestamptz', notNull: true, default: 'now()' },
},
posts: {
id: { type: 'serial', primaryKey: true },
userId: { type: 'integer', notNull: true, references: 'users.id' },
title: { type: 'text', notNull: true },
createdAt: { type: 'timestamptz', notNull: true, default: 'now()' },
},
});Then create the tables and generate the client:
npx turbine push # apply the schema to the database
npx turbine generate # write the typed clientpush is for development. In production, ship migrations.
generate writes three files to ./generated/turbine/: types.ts (entity and input types), metadata.ts (runtime schema metadata) and index.ts (the typed client).
4. Run a query#
import { turbine } from './generated/turbine/index.js';
const db = turbine(); // reads DATABASE_URL from the environment
const admins = await db.users.findMany({
where: { role: 'admin' },
orderBy: { createdAt: 'desc' },
limit: 10,
});
console.log(admins[0]?.email);
await db.disconnect();Every column, operator and relation autocompletes, and results are typed: admins[0].createdAt is a Date.
5. Load relations#
const users = await db.users.findMany({
with: {
posts: { orderBy: { createdAt: 'desc' }, limit: 5 },
},
});
users[0]?.posts[0]?.title; // typed all the way downRelation names come from foreign keys: posts.user_id gives posts.user and users.posts. To rename one, see relationNames.
Turbine usually loads the whole with tree in one SQL statement, a plan that relies on foreign-key indexes. npx turbine doctor lists missing ones, and Load strategies explains when a relation loads in a second query.
Next steps#
- Migrate from Prisma or Migrate from Drizzle if you are porting an existing app.
- Reading and writing and Filtering and sorting: every query method, operator and option.
- Schema: define tables in TypeScript.
- Migrations: the production workflow.
- Transactions: savepoints, isolation levels and RLS sessions.
- CLI: every command and flag.
- Turbine for AI agents: the MCP server and an agent-instructions snippet.