Drizzle

Drizzle ORM integration for PostgreSQL, MySQL, and SQLite.

@miiajs/drizzle integrates Drizzle ORM with MiiaJS, providing connection management, retry logic, and DI-based access to the database.

Installation

Install the core package and a database driver:

npm install @miiajs/drizzle drizzle-orm postgres

Setup

Configure the module

import { Module } from '@miiajs/core'
import { DrizzleModule } from '@miiajs/drizzle'

@Module({
  imports: [
    DrizzleModule.configure({
      dialect: 'postgres',
      connection: { url: 'postgres://localhost:5432/mydb' },
    }),
  ],
})
class AppModule {}

With ConfigService (from @miiajs/config):

import { ConfigService } from '@miiajs/config'

DrizzleModule.configure((resolve) => {
  const config = resolve(ConfigService)
  return {
    dialect: 'postgres',
    connection: {
      url: config.getOrThrow('DATABASE_URL'),
      retry: { attempts: 10, delay: 10_000 },
    },
  }
})

Configuration options

interface DrizzleModuleOptions {
  dialect: 'postgres' | 'mysql' | 'sqlite'
  connection: {
    url: string
    retry?: {
      attempts?: number  // Default: 10
      delay?: number     // Default: 10_000 ms
    }
  }
  schema?: Record<string, unknown>
  casing?: 'snake_case' | 'camelCase'
}

Define schemas

Use standard Drizzle table definitions:

import { pgTable, serial, varchar, timestamp } from 'drizzle-orm/pg-core'
import { sql } from 'drizzle-orm'

export const users = pgTable('users', {
  id: serial('id').primaryKey(),
  name: varchar('name', { length: 255 }).notNull(),
  email: varchar('email', { length: 255 }).notNull().unique(),
  role: varchar('role', { length: 50 }).notNull().default('user'),
  createdAt: timestamp('created_at').default(sql`now()`),
})

Register schemas

In configure

DrizzleModule.configure({
  dialect: 'postgres',
  connection: { url: DATABASE_URL },
  schema: { users, posts },
})

Per feature module

@Module({
  imports: [DrizzleModule.register({ users })],
  controllers: [UserController],
  providers: [UserService],
})
class UserModule {}

@Module({
  imports: [DrizzleModule.register({ posts })],
  controllers: [PostController],
  providers: [PostService],
})
class PostModule {}

Schemas from multiple register() calls are merged automatically.

Use in services

Use injectDrizzle() or inject('DRIZZLE') to access the database directly:

import { Injectable } from '@miiajs/core'
import { injectDrizzle } from '@miiajs/drizzle'
import { eq } from 'drizzle-orm'
import { users } from './user.schema.js'

@Injectable()
class UserService {
  private db = injectDrizzle()

  async findAll() {
    return this.db.select().from(users)
  }

  async findById(id: number) {
    const [user] = await this.db
      .select()
      .from(users)
      .where(eq(users.id, id))
    return user ?? null
  }

  async create(data: { name: string; email: string }) {
    const [user] = await this.db
      .insert(users)
      .values(data)
      .returning()
    return user
  }

  async update(id: number, data: Partial<{ name: string; email: string }>) {
    const [user] = await this.db
      .update(users)
      .set(data)
      .where(eq(users.id, id))
      .returning()
    return user
  }

  async delete(id: number) {
    await this.db.delete(users).where(eq(users.id, id))
  }
}

Decorator style:

import { Injectable } from '@miiajs/core'
import { InjectDrizzle } from '@miiajs/drizzle'

@Injectable()
class UserService {
  @InjectDrizzle() private db!: any
}

Relational queries

When schemas include relations, use db.query for the relational query builder:

import { relations } from 'drizzle-orm'

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

// Register both tables and relations
DrizzleModule.register({ users, posts, usersRelations, postsRelations })
// Query with eager loading
const usersWithPosts = await this.db.query.users.findMany({
  with: { posts: true },
})

Multiple connections

Use named connections for multi-database setups:

// Root module
@Module({
  imports: [
    DrizzleModule.configure({
      dialect: 'postgres',
      connection: { url: MAIN_DB },
    }),
    DrizzleModule.configure({
      dialect: 'postgres',
      connection: { url: ANALYTICS_DB },
    }, 'analytics'),
    UserModule,
    AnalyticsModule,
  ],
})
class AppModule {}

Register schemas per connection:

// UserModule
DrizzleModule.register({ users })          // default connection

// AnalyticsModule
DrizzleModule.register({ events }, 'analytics')  // named connection

Inject specific connections:

@Injectable()
class AnalyticsService {
  private db = injectDrizzle('analytics')

  async getEvents() {
    return this.db.select().from(events)
  }
}

Supported databases

DialectDriverVersion
postgrespostgres>= 3.0.0
mysqlmysql2>= 3.0.0
sqlitebetter-sqlite3>= 11.0.0

All drivers are optional peer dependencies — install only what you need.

Connection lifecycle

  • onInit — establishes connection with automatic retry on transient errors (ECONNREFUSED, ECONNRESET, ETIMEDOUT, ENOTFOUND, EAI_AGAIN)
  • onDestroy — closes the connection gracefully

Exports

import { DrizzleModule, injectDrizzle, InjectDrizzle, getDrizzleToken } from '@miiajs/drizzle'
import type { Dialect, DrizzleModuleOptions } from '@miiajs/drizzle'