LogoNebaura Docs

Databases

Warp supports multiple database providers with Drizzle ORM for type-safe database access.

Supported Providers

Choose the database that fits your needs:

  • Supabase - PostgreSQL with built-in auth, storage, and real-time
  • Neon - Serverless PostgreSQL with autoscaling
  • PlanetScale - MySQL-compatible serverless database
  • Turso - Edge SQLite with global replication
  • PostgreSQL - Self-hosted or managed PostgreSQL

Drizzle ORM

All databases use Drizzle ORM for:

  • Type Safety: Full TypeScript support with autocomplete
  • SQL-like Syntax: Familiar query builder
  • Migrations: Automatic migration generation
  • Relations: Easy foreign key relationships
  • Performance: Lightweight with no overhead

Quick Start

1. Choose Your Database

Select a provider during project creation:

npx create-warp@latest my-app
# Select database provider when prompted

2. Configure Environment

Add your database URL to .env.local:

DATABASE_URL="postgresql://..."

3. Define Schema

Create your schema in db/schema.ts:

import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: uuid('id').primaryKey().defaultRandom(),
  email: text('email').notNull().unique(),
  name: text('name'),
  createdAt: timestamp('created_at').defaultNow(),
});

4. Generate Migrations

pnpm db:generate

5. Run Migrations

pnpm db:migrate

6. Use in Your App

import { db } from '@/db';
import { users } from '@/db/schema';

// Insert
await db.insert(users).values({
  email: 'user@example.com',
  name: 'John Doe',
});

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

// Query with conditions
const user = await db.select()
  .from(users)
  .where(eq(users.email, 'user@example.com'));

Database Scripts

Warp includes helpful scripts in package.json:

# Generate migration from schema changes
pnpm db:generate

# Run migrations
pnpm db:migrate

# Push schema directly (dev only)
pnpm db:push

# Open Drizzle Studio
pnpm db:studio

Drizzle Studio

Visual database browser at https://local.drizzle.studio:

pnpm db:studio

Features:

  • Browse tables and data
  • Run queries
  • Edit records
  • View relationships
  • Inspect schema

Schema Organization

Organize your schema by feature:

db/
  index.ts          # Database client
  schema.ts         # Main schema exports
  schema/
    users.ts        # User tables
    products.ts     # Product tables
    orders.ts       # Order tables

Example db/schema.ts:

export * from './schema/users';
export * from './schema/products';
export * from './schema/orders';

Common Patterns

Timestamps

Add created/updated timestamps:

export const posts = pgTable('posts', {
  id: uuid('id').primaryKey().defaultRandom(),
  title: text('title').notNull(),
  createdAt: timestamp('created_at').defaultNow().notNull(),
  updatedAt: timestamp('updated_at').defaultNow().notNull(),
});

Relations

Define foreign keys and relations:

import { relations } from 'drizzle-orm';

export const users = pgTable('users', {
  id: uuid('id').primaryKey().defaultRandom(),
  name: text('name').notNull(),
});

export const posts = pgTable('posts', {
  id: uuid('id').primaryKey().defaultRandom(),
  userId: uuid('user_id').references(() => users.id).notNull(),
  title: text('title').notNull(),
});

export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
}));

export const postsRelations = relations(posts, ({ one }) => ({
  user: one(users, {
    fields: [posts.userId],
    references: [users.id],
  }),
}));

Enums

Type-safe enums:

import { pgEnum } from 'drizzle-orm/pg-core';

export const roleEnum = pgEnum('role', ['admin', 'user', 'guest']);

export const users = pgTable('users', {
  id: uuid('id').primaryKey().defaultRandom(),
  role: roleEnum('role').default('user').notNull(),
});

Querying

Basic Queries

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

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

// Where clause
import { eq, gt, and, or } from 'drizzle-orm';

const activeUsers = await db.select()
  .from(users)
  .where(eq(users.active, true));

// Multiple conditions
const result = await db.select()
  .from(users)
  .where(and(
    eq(users.active, true),
    gt(users.createdAt, new Date('2024-01-01'))
  ));

Joins

const postsWithAuthors = await db.select({
  post: posts,
  author: users,
})
  .from(posts)
  .leftJoin(users, eq(posts.userId, users.id));

Ordering & Pagination

import { desc, asc } from 'drizzle-orm';

const recentPosts = await db.select()
  .from(posts)
  .orderBy(desc(posts.createdAt))
  .limit(10)
  .offset(0);

Transactions

Execute multiple operations atomically:

await db.transaction(async (tx) => {
  const user = await tx.insert(users).values({
    email: 'user@example.com',
  }).returning();
  
  await tx.insert(posts).values({
    userId: user[0].id,
    title: 'First post',
  });
});

Type Safety

Get full TypeScript types:

import { type InferSelectModel, type InferInsertModel } from 'drizzle-orm';
import { users } from './schema';

// Infer types from schema
type User = InferSelectModel<typeof users>;
type NewUser = InferInsertModel<typeof users>;

// Use in your application
function createUser(data: NewUser): Promise<User> {
  // ...
}

Best Practices

1. Use Prepared Statements

For repeated queries:

const getUserByEmail = db.select()
  .from(users)
  .where(eq(users.email, placeholder('email')))
  .prepare('get_user_by_email');

const user = await getUserByEmail.execute({ email: 'user@example.com' });

2. Index Your Queries

Add indexes for frequently queried columns:

import { index } from 'drizzle-orm/pg-core';

export const users = pgTable('users', {
  id: uuid('id').primaryKey().defaultRandom(),
  email: text('email').notNull(),
}, (table) => ({
  emailIdx: index('email_idx').on(table.email),
}));

3. Validate Data

Use Zod for validation before inserting:

import { z } from 'zod';

const userSchema = z.object({
  email: z.string().email(),
  name: z.string().min(1),
});

const data = userSchema.parse(input);
await db.insert(users).values(data);

4. Handle Errors

Wrap database operations in try-catch:

try {
  await db.insert(users).values(data);
} catch (error) {
  if (error.code === '23505') {
    // Unique constraint violation
    throw new Error('Email already exists');
  }
  throw error;
}

Migration Strategy

Development

Use db:push for rapid iteration:

pnpm db:push

Production

Always use migrations:

# Generate migration
pnpm db:generate

# Review migration file in drizzle/

# Apply migration
pnpm db:migrate

Resources