Database
The boilerplate includes a pre-built users table. All other tables are added as you build features with Claude Code.
The project uses Drizzle ORM with Neon DB (serverless Postgres). All DB access goes through the repository layer in each module -- never directly from components or routes.
Schema
Tables are defined in modules/<name>/<name>.schema.ts:
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core';
export const postTable = pgTable('posts', {
id: uuid('id').primaryKey().defaultRandom(),
title: text('title').notNull(),
content: text('content').notNull(),
authorId: uuid('author_id').notNull(),
createdAt: timestamp('created_at').notNull().defaultNow(),
updatedAt: timestamp('updated_at').notNull().defaultNow(),
});Naming rules:
- Table exports:
camelCase+Tablesuffix (postTable,userTable) - Table names: plural snake_case (
posts,users) - Column names: snake_case (
created_at,author_id)
Relations
Relations live in modules/<name>/<name>.relations.ts:
import { relations } from 'drizzle-orm';
import { postTable } from './post.schema';
import { userTable } from '@/modules/user/user.schema';
export const postRelations = relations(postTable, ({ one }) => ({
author: one(userTable, {
fields: [postTable.authorId],
references: [userTable.id],
}),
}));Both the schema and relations must be registered in db/drizzle.ts.
Migrations
After changing a schema:
npm run db:generate # Generate SQL migration file
npm run db:migrate # Apply to databaseFor quick local iteration (no migration file):
npm run db:push # Dev only -- syncs schema directlyUse npm run db:studio to inspect your data in a visual UI.
Repository pattern
All queries live in modules/<name>/<name>.repo.ts. No business logic here -- only typed DB calls:
import { db } from '@/db/drizzle';
import { postTable } from './post.schema';
import { eq } from 'drizzle-orm';
import type { NewPost } from './post.types';
export const postRepo = {
async findAll() {
return db.select().from(postTable).orderBy(postTable.createdAt);
},
async findById(id: string) {
const rows = await db.select().from(postTable).where(eq(postTable.id, id));
return rows[0] ?? null;
},
async create(data: NewPost) {
const rows = await db.insert(postTable).values(data).returning();
return rows[0];
},
};Connecting to Neon
Set DATABASE_URL in .env to your Neon connection string. The DB client in db/drizzle.ts uses the @neondatabase/serverless driver, which works in both Node.js and edge runtimes.
You can point DATABASE_URL at any Postgres instance -- local, Supabase, Railway, etc.