feat(api): Drizzle + libsql DB layer with better-auth schema and migrations

This commit is contained in:
Bas van Rossem
2026-06-17 13:40:31 +02:00
parent 62c8597068
commit e8aa2c67e8
10 changed files with 528 additions and 2 deletions

View File

@@ -0,0 +1,7 @@
import { drizzle } from 'drizzle-orm/libsql';
import { createClient } from '@libsql/client';
import { env } from '../env';
import * as schema from './schema';
const client = createClient({ url: env.DATABASE_URL });
export const db = drizzle(client, { schema });

View File

@@ -0,0 +1,24 @@
import { drizzle } from 'drizzle-orm/libsql';
import { migrate } from 'drizzle-orm/libsql/migrator';
import { createClient } from '@libsql/client';
import { env } from '../env';
export async function runMigrations(): Promise<void> {
const client = createClient({ url: env.DATABASE_URL });
const db = drizzle(client);
await migrate(db, { migrationsFolder: './drizzle' });
client.close();
}
// Allow running directly: `tsx src/db/migrate.ts`
if (import.meta.url === `file://${process.argv[1]}`) {
runMigrations()
.then(() => {
console.log('Migrations applied.');
process.exit(0);
})
.catch((err) => {
console.error(err);
process.exit(1);
});
}

51
apps/api/src/db/schema.ts Normal file
View File

@@ -0,0 +1,51 @@
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
export const user = sqliteTable('user', {
id: text('id').primaryKey(),
name: text('name').notNull(),
email: text('email').notNull().unique(),
emailVerified: integer('email_verified', { mode: 'boolean' }).notNull().default(false),
image: text('image'),
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
});
export const session = sqliteTable('session', {
id: text('id').primaryKey(),
expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull(),
token: text('token').notNull().unique(),
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
ipAddress: text('ip_address'),
userAgent: text('user_agent'),
userId: text('user_id')
.notNull()
.references(() => user.id, { onDelete: 'cascade' }),
});
export const account = sqliteTable('account', {
id: text('id').primaryKey(),
accountId: text('account_id').notNull(),
providerId: text('provider_id').notNull(),
userId: text('user_id')
.notNull()
.references(() => user.id, { onDelete: 'cascade' }),
accessToken: text('access_token'),
refreshToken: text('refresh_token'),
idToken: text('id_token'),
accessTokenExpiresAt: integer('access_token_expires_at', { mode: 'timestamp' }),
refreshTokenExpiresAt: integer('refresh_token_expires_at', { mode: 'timestamp' }),
scope: text('scope'),
password: text('password'),
createdAt: integer('created_at', { mode: 'timestamp' }).notNull(),
updatedAt: integer('updated_at', { mode: 'timestamp' }).notNull(),
});
export const verification = sqliteTable('verification', {
id: text('id').primaryKey(),
identifier: text('identifier').notNull(),
value: text('value').notNull(),
expiresAt: integer('expires_at', { mode: 'timestamp' }).notNull(),
createdAt: integer('created_at', { mode: 'timestamp' }),
updatedAt: integer('updated_at', { mode: 'timestamp' }),
});