Drizzle Adapter
April 15, 2025
Installation
Install Drizzle ORM and the appropriate database driver:
# For PostgreSQL
npm install drizzle-orm pg @types/pg
# For MySQL
npm install drizzle-orm mysql2 @types/mysql2
# For SQLite
npm install drizzle-orm better-sqlite3 @types/better-sqlite3Configuration
- Define your schema using Drizzle's schema builder:
// schema.ts
import {
pgTable,
uuid,
text,
timestamp,
json,
boolean,
} from 'drizzle-orm/pg-core';
export const subject = pgTable('subject', {
id: text('id').primaryKey(),
isIdentified: boolean('is_identified').notNull(),
externalId: text('external_id'),
identityProvider: text('identity_provider'),
lastIpAddress: text('last_ip_address'),
createdAt: timestamp('created_at').notNull(),
updatedAt: timestamp('updated_at').notNull(),
subjectTimezone: text('subject_timezone'),
});
export const consentPurpose = pgTable('consent_purpose', {
id: text('id').primaryKey(),
code: text('code').notNull(),
name: text('name').notNull(),
description: text('description').notNull(),
isEssential: boolean('is_essential').notNull(),
dataCategory: text('data_category'),
legalBasis: text('legal_basis'),
isActive: boolean('is_active').notNull(),
createdAt: timestamp('created_at').notNull(),
updatedAt: timestamp('updated_at').notNull(),
});
export const consentPolicy = pgTable('consent_policy', {
id: text('id').primaryKey(),
version: text('version').notNull(),
type: text('type').notNull(),
name: text('name').notNull(),
effectiveDate: timestamp('effective_date').notNull(),
expirationDate: timestamp('expiration_date'),
content: text('content').notNull(),
contentHash: text('content_hash').notNull(),
isActive: boolean('is_active').notNull(),
createdAt: timestamp('created_at').notNull(),
});
export const domain = pgTable('domain', {
id: text('id').primaryKey(),
name: text('name').notNull().unique(),
description: text('description'),
allowedOrigins: json('allowed_origins'),
isVerified: boolean('is_verified').notNull(),
isActive: boolean('is_active').notNull(),
createdAt: timestamp('created_at').notNull(),
updatedAt: timestamp('updated_at'),
});
export const consent = pgTable('consent', {
id: text('id').primaryKey(),
subjectId: text('subject_id')
.notNull()
.references(() => subject.id, { onDelete: 'cascade' }),
domainId: text('domain_id')
.notNull()
.references(() => domain.id, { onDelete: 'cascade' }),
purposeIds: json('purpose_ids'),
metadata: json('metadata'),
policyId: text('policy_id').references(() => consentPolicy.id, {
onDelete: 'cascade',
}),
ipAddress: text('ip_address'),
userAgent: text('user_agent'),
status: text('status').notNull(),
withdrawalReason: text('withdrawal_reason'),
givenAt: timestamp('given_at').notNull(),
validUntil: timestamp('valid_until'),
isActive: boolean('is_active').notNull(),
});
export const consentRecord = pgTable('consent_record', {
id: text('id').primaryKey(),
subjectId: text('subject_id')
.notNull()
.references(() => subject.id, { onDelete: 'cascade' }),
consentId: text('consent_id').references(() => consent.id, {
onDelete: 'cascade',
}),
actionType: text('action_type').notNull(),
details: json('details'),
createdAt: timestamp('created_at').notNull(),
});
export const auditLog = pgTable('audit_log', {
id: text('id').primaryKey(),
entityType: text('entity_type').notNull(),
entityId: text('entity_id').notNull(),
actionType: text('action_type').notNull(),
subjectId: text('subject_id').references(() => subject.id, {
onDelete: 'cascade',
}),
ipAddress: text('ip_address'),
userAgent: text('user_agent'),
changes: json('changes'),
metadata: json('metadata'),
createdAt: timestamp('created_at').notNull(),
eventTimezone: text('event_timezone').notNull(),
});- Configure the c15t instance with the Drizzle adapter:
import { c15tInstance } from '@c15t/backend';
import { drizzleAdapter } from '@c15t/backend/db/adapters/drizzle';
import { drizzle } from 'drizzle-orm/node-postgres';
import { Pool } from 'pg';
import * as tables from '@/schema';
// Create a PostgreSQL connection
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
});
// Initialize Drizzle with the connection
const db = drizzle(pool);
// Create the c15t instance
const instance = c15tInstance({
baseURL: 'http://localhost:3000',
database: drizzleAdapter(db as unknown as { [p: string]: unknown }, {
provider: 'pg',
schema: {
...tables,
},
}),
});MySQL Configuration
import { drizzle } from 'drizzle-orm/mysql2';
import mysql from 'mysql2/promise';
const connection = await mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'c15t',
});
const db = drizzle(connection);
const instance = c15tInstance({
database: drizzleAdapter({ client: db }),
});SQLite Configuration
import { drizzle } from 'drizzle-orm/better-sqlite3';
import Database from 'better-sqlite3';
const sqlite = new Database('database.db');
const db = drizzle(sqlite);
const instance = c15tInstance({
database: drizzleAdapter({ client: db }),
});Usage Examples
Basic CRUD Operations
// Create a new record
const user = await instance.database.create('users', {
name: 'John Doe',
email: 'john@example.com',
});
// Find records
const users = await instance.database.find('users', {
where: { email: 'john@example.com' },
orderBy: { createdAt: 'desc' },
limit: 10,
});
// Update a record
const updatedUser = await instance.database.update(
'users',
{ where: { id: user.id } },
{ name: 'John Smith' }
);
// Delete a record
await instance.database.delete('users', { where: { id: user.id } });Transactions
await instance.database.transaction(async (trx) => {
const user = await trx.create('users', {
name: 'Alice',
email: 'alice@example.com',
});
await trx.create('profiles', {
userId: user.id,
bio: 'Software engineer',
});
});Migrations
Use Drizzle Kit for schema migrations:
npm install -D drizzle-kit
# Generate a migration
npx drizzle-kit generate:pg
# Apply migrations
npx drizzle-kit push:pgType Safety
The Drizzle adapter provides excellent type safety:
import { users } from './schema';
import { InferModel } from 'drizzle-orm';
// Infer types from your schema
type User = InferModel<typeof users>;
// Type-safe operations
const users = await instance.database.find<User>('users', {
where: { email: 'john@example.com' },
});Best Practices
- Define schema using Drizzle's builders - Leverage type safety and schema validation
- Use prepared statements - Drizzle uses prepared statements for all queries
- Implement connection pooling - Configure appropriate pool sizes for production
- Use migrations for schema changes - Manage schema changes with Drizzle Kit
Limitations
- Some complex queries may require direct Drizzle client usage
- Table names must match schema definitions