Database Migrations
Database Migrations
Manacore uses Drizzle ORM for database management with PostgreSQL.
Overview
Each backend service has its own database and schema:
| Service | Database | Port |
|---|---|---|
| mana-core-auth | manacore | 3001 |
| chat | chat | 3002 |
| zitare | zitare | 3007 |
| contacts | contacts | 3015 |
| calendar | calendar | 3014 |
Quick Commands
# Push schema changes (development)pnpm --filter @chat/backend db:push
# Generate migration filespnpm --filter @chat/backend db:generate
# Run migrationspnpm --filter @chat/backend db:migrate
# Open Drizzle Studio (database GUI)pnpm --filter @chat/backend db:studioDevelopment Workflow
For local development, use db:push to quickly sync schema changes:
-
Modify your schema
Edit the schema file (e.g.,
src/drizzle/schema.ts):export const users = pgTable('users', {id: uuid('id').primaryKey().defaultRandom(),email: varchar('email', { length: 255 }).notNull().unique(),name: varchar('name', { length: 255 }),createdAt: timestamp('created_at').defaultNow(),// Add new fieldavatarUrl: varchar('avatar_url', { length: 500 }),}); -
Push changes
Terminal window pnpm --filter @chat/backend db:push -
Verify in Studio
Terminal window pnpm --filter @chat/backend db:studio
Production Migrations
For production, use proper migration files:
-
Generate migration
Terminal window pnpm --filter @chat/backend db:generateThis creates a timestamped SQL file in
drizzle/migrations/. -
Review migration
Check the generated SQL:
-- 0001_add_avatar_url.sqlALTER TABLE "users" ADD COLUMN "avatar_url" varchar(500); -
Run migration
Terminal window pnpm --filter @chat/backend db:migrate
Schema Patterns
Basic Table
import { pgTable, uuid, varchar, timestamp, boolean } from 'drizzle-orm/pg-core';
export const posts = pgTable('posts', { id: uuid('id').primaryKey().defaultRandom(), title: varchar('title', { length: 255 }).notNull(), content: text('content'), published: boolean('published').default(false), createdAt: timestamp('created_at').defaultNow(), updatedAt: timestamp('updated_at').defaultNow(),});Foreign Keys
export const comments = pgTable('comments', { id: uuid('id').primaryKey().defaultRandom(), postId: uuid('post_id') .notNull() .references(() => posts.id, { onDelete: 'cascade' }), userId: uuid('user_id') .notNull() .references(() => users.id), content: text('content').notNull(), createdAt: timestamp('created_at').defaultNow(),});Indexes
import { pgTable, uuid, varchar, index } from 'drizzle-orm/pg-core';
export const users = pgTable('users', { id: uuid('id').primaryKey().defaultRandom(), email: varchar('email', { length: 255 }).notNull().unique(), organizationId: uuid('organization_id'),}, (table) => ({ emailIdx: index('users_email_idx').on(table.email), orgIdx: index('users_org_idx').on(table.organizationId),}));Enums
import { pgEnum } from 'drizzle-orm/pg-core';
export const roleEnum = pgEnum('role', ['user', 'admin', 'moderator']);
export const users = pgTable('users', { id: uuid('id').primaryKey().defaultRandom(), role: roleEnum('role').default('user'),});Drizzle Configuration
Each backend has a drizzle.config.ts:
import { defineConfig } from 'drizzle-kit';
export default defineConfig({ schema: './src/drizzle/schema.ts', out: './drizzle/migrations', dialect: 'postgresql', dbCredentials: { url: process.env.DATABASE_URL!, }, verbose: true, strict: true,});Connecting in Services
NestJS Module
import { DrizzleModule } from '@manacore/shared-drizzle';
@Module({ imports: [ DrizzleModule.forRootAsync({ imports: [ConfigModule], useFactory: (config: ConfigService) => ({ connectionString: config.get('DATABASE_URL'), }), inject: [ConfigService], }), ],})export class AppModule {}Using in Services
import { Inject, Injectable } from '@nestjs/common';import { DRIZZLE } from '@manacore/shared-drizzle';import { eq } from 'drizzle-orm';import { users } from '../drizzle/schema';
@Injectable()export class UsersService { constructor(@Inject(DRIZZLE) private db: DrizzleDB) {}
async findById(id: string) { const [user] = await this.db .select() .from(users) .where(eq(users.id, id)); return user; }
async create(data: { email: string; name: string }) { const [user] = await this.db .insert(users) .values(data) .returning(); return user; }}Troubleshooting
”relation does not exist”
Schema not pushed:
pnpm --filter @chat/backend db:push --forceMigration conflicts
Reset and regenerate:
# Delete old migrationsrm -rf apps/chat/apps/backend/drizzle/migrations/*
# Regeneratepnpm --filter @chat/backend db:generateConnection refused
Check PostgreSQL is running:
pnpm docker:updocker ps | grep postgresBest Practices
- Always review generated migrations before running in production
- Use transactions for multi-step operations
- Test migrations on a copy of production data
- Keep schemas in sync across services that share data
- Use
db:pushfor development,db:migratefor production