Skip to main content

Build your first app

This guide will walk you through creating a complete database application with Drizzle ORM, from installation to executing your first queries.
We’ll use PostgreSQL with node-postgres driver in this guide, but Drizzle works the same way with MySQL and SQLite.

Prerequisites

Before starting, ensure you have:
  • Node.js 18+ or Bun installed
  • A PostgreSQL database running (or use a cloud provider like Neon, Supabase, or Vercel)
  • Basic TypeScript knowledge

Step-by-step tutorial

1

Install dependencies

Create a new project and install Drizzle ORM with the PostgreSQL driver:
npm install drizzle-orm pg
npm install -D drizzle-kit @types/pg tsx typescript
drizzle-orm: Core ORM librarypg: PostgreSQL driverdrizzle-kit: CLI for migrationstsx: TypeScript execution runtime
2

Set up environment variables

Create a .env file in your project root:
.env
DATABASE_URL=postgresql://user:password@localhost:5432/mydb
For cloud databases, use the connection string from your provider:
DATABASE_URL=postgresql://user:[email protected]/neondb?sslmode=require
Add .env to your .gitignore to keep credentials secure.
3

Define your database schema

Create src/db/schema.ts and define your tables:
src/db/schema.ts
import { pgTable, serial, text, varchar, timestamp, integer } from 'drizzle-orm/pg-core';
import { relations } from 'drizzle-orm';

// Users table
export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  email: varchar('email', { length: 255 }).notNull().unique(),
  name: text('name').notNull(),
  createdAt: timestamp('created_at').defaultNow().notNull(),
});

// Posts table
export const posts = pgTable('posts', {
  id: serial('id').primaryKey(),
  title: varchar('title', { length: 255 }).notNull(),
  content: text('content').notNull(),
  authorId: integer('author_id')
    .notNull()
    .references(() => users.id, { onDelete: 'cascade' }),
  publishedAt: timestamp('published_at'),
  createdAt: timestamp('created_at').defaultNow().notNull(),
});

// Define relations for relational queries
export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));

export const postsRelations = relations(posts, ({ one }) => ({
  author: one(users, {
    fields: [posts.authorId],
    references: [users.id],
  }),
}));
Relations are optional but enable powerful relational queries. You can skip them and use SQL joins instead.
4

Configure Drizzle Kit

Create drizzle.config.ts in your project root:
drizzle.config.ts
import { defineConfig } from 'drizzle-kit';

export default defineConfig({
  schema: './src/db/schema.ts',
  out: './drizzle',
  dialect: 'postgresql',
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
});
This tells Drizzle Kit where to find your schema and how to connect to your database.
5

Generate and run migrations

Generate SQL migrations from your schema:
npx drizzle-kit generate
This creates migration files in the ./drizzle directory. Apply them to your database:
npx drizzle-kit migrate
During development, you can use npx drizzle-kit push to apply schema changes directly without generating migration files.
6

Initialize database connection

Create src/db/index.ts to set up your database connection:
src/db/index.ts
import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';
import * as schema from './schema';

// Create PostgreSQL connection pool
const pool = new Pool({
  connectionString: process.env.DATABASE_URL!,
});

// Initialize Drizzle with schema for relational queries
export const db = drizzle(pool, { schema });
Passing the schema enables relational queries via db.query. If you only need SQL-like queries, you can omit it: drizzle(pool)
7

Execute your first queries

Create src/index.ts to test your setup:
src/index.ts
import { db } from './db';
import { users, posts } from './db/schema';
import { eq } from 'drizzle-orm';

async function main() {
  // Insert a new user
  const [newUser] = await db
    .insert(users)
    .values({
      email: '[email protected]',
      name: 'John Doe',
    })
    .returning();

  console.log('Created user:', newUser);

  // Insert a post for the user
  const [newPost] = await db
    .insert(posts)
    .values({
      title: 'My First Post',
      content: 'Hello, Drizzle ORM!',
      authorId: newUser.id,
      publishedAt: new Date(),
    })
    .returning();

  console.log('Created post:', newPost);

  // Query all users with their posts (relational query)
  const usersWithPosts = await db.query.users.findMany({
    with: {
      posts: true,
    },
  });

  console.log('Users with posts:', JSON.stringify(usersWithPosts, null, 2));

  // Alternative: SQL-like join query
  const usersAndPosts = await db
    .select()
    .from(users)
    .leftJoin(posts, eq(posts.authorId, users.id));

  console.log('SQL-like query:', usersAndPosts);
}

main()
  .catch(console.error)
  .finally(() => process.exit());
8

Run your application

Execute your TypeScript file:
npx tsx src/index.ts
You should see output showing the created user and post, followed by the query results.
Add a script to your package.json for convenience:
{
  "scripts": {
    "dev": "tsx watch src/index.ts",
    "db:generate": "drizzle-kit generate",
    "db:migrate": "drizzle-kit migrate",
    "db:push": "drizzle-kit push",
    "db:studio": "drizzle-kit studio"
  }
}

Query examples

Now that you have a working setup, explore common query patterns:

Insert operations

// Insert one user
const user = await db.insert(users).values({
  email: '[email protected]',
  name: 'Alice Smith',
}).returning();

Select queries

// Select all users
const allUsers = await db.select().from(users);

// Select specific columns
const userEmails = await db
  .select({ 
    id: users.id, 
    email: users.email 
  })
  .from(users);

Update operations

// Update a specific user
const updated = await db
  .update(users)
  .set({ name: 'John Updated' })
  .where(eq(users.id, 1))
  .returning();

Delete operations

// Delete a specific user
const deleted = await db
  .delete(users)
  .where(eq(users.id, 1))
  .returning();

Joins and aggregations

import { eq } from 'drizzle-orm';

// Join users and posts
const result = await db
  .select({
    userName: users.name,
    postTitle: posts.title,
  })
  .from(users)
  .innerJoin(posts, eq(posts.authorId, users.id));

Explore Drizzle Studio

Drizzle Kit includes a visual database browser:
npx drizzle-kit studio
This opens a web interface at https://local.drizzle.studio where you can:
  • Browse all tables and data
  • Execute queries visually
  • Edit records directly
  • View table relationships
Drizzle Studio is perfect for development and debugging. It reads your schema file for full type information.

Database-specific guides

This quickstart used PostgreSQL, but Drizzle works identically with other databases:

MySQL

Complete guide for MySQL and MariaDB

SQLite

Guide for SQLite, including D1 and Turso

All Drivers

Overview of all supported database drivers

Next steps

Now that you have a working application, dive deeper into Drizzle’s features:

Schema Declaration

Learn advanced schema features: constraints, indexes, custom types

Queries

Master complex queries, joins, and aggregations

Relations

Build type-safe relational queries

Migrations

Advanced migration workflows and strategies

Transactions

Execute atomic operations with transactions

Best Practices

Production-ready patterns and optimizations

Common patterns

Connection pooling

For production applications, configure connection pooling:
import { Pool } from 'pg';

const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
  max: 20, // Maximum connections
  idleTimeoutMillis: 30000,
  connectionTimeoutMillis: 2000,
});

export const db = drizzle(pool, { schema });

Environment-based configuration

Manage different database configurations per environment:
src/db/config.ts
const config = {
  development: {
    url: process.env.DEV_DATABASE_URL!,
    max: 5,
  },
  production: {
    url: process.env.DATABASE_URL!,
    max: 20,
    ssl: { rejectUnauthorized: false },
  },
};

const env = process.env.NODE_ENV || 'development';
export const dbConfig = config[env];

Prepared statements

Optimize repeated queries with prepared statements:
import { eq } from 'drizzle-orm';

const getUserById = db
  .select()
  .from(users)
  .where(eq(users.id, placeholder('id')))
  .prepare('get_user_by_id');

// Execute multiple times efficiently
const user1 = await getUserById.execute({ id: 1 });
const user2 = await getUserById.execute({ id: 2 });

Troubleshooting

Ensure your database is running and the connection string is correct. Check:
  • Database host and port
  • Username and password
  • Database name
  • SSL requirements (add ?sslmode=require for cloud databases)
Make sure your schema types are correctly imported:
import { users } from './db/schema'; // Not './db/schema.ts'
Restart your TypeScript server in VSCode: Cmd+Shift+P > “Restart TS Server”
If migrations fail:
  1. Check drizzle.config.ts points to the correct schema file
  2. Verify database credentials
  3. Review generated SQL in ./drizzle folder
  4. Run npx drizzle-kit drop to reset (development only)
Need help? Join our Discord community or check out GitHub Discussions for support.

Build docs developers (and LLMs) love