Relational Databases
Providers backed by Kysely
Relational Databases
Any Relational databases supported by Kysely can be used with Better Blog.
Environment Variables
DATABASE_HOST=
DATABASE_NAME=
DATABASE_USER=
DATABASE_PASSWORD=
Configuration
This adapter supports the same first party dialects that Kysely (as of v0.28.5) supports: PostgreSQL, MySQL, and SQLite. The examples below use PostgreSQL with the pg client.
pnpm install pg
pnpm install -D @types/pg
//./lib/blog-data-provider.ts
import { createSQLProvider } from "better-blog/server/sql"
import { Pool } from "pg";
export function createBlogDataProvider() {
return createSQLProvider({
database: new Pool({
host: process.env.DATABASE_HOST,
database: process.env.DATABASE_NAME,
user: process.env.DATABASE_USER,
password: process.env.DATABASE_PASSWORD,
})
})
}
Schema
//lib/migrations/001_create_blog_tables.ts
import { Kysely, sql } from 'kysely'
export async function up(db: Kysely<any>): Promise<void> {
// Create Post table
await db.schema
.createTable('Post')
.addColumn('id', 'uuid', (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`))
.addColumn('authorId', 'text') // nullable by default
.addColumn('defaultLocale', 'text', (col) => col.notNull().defaultTo('en'))
.addColumn('title', 'text', (col) => col.notNull())
.addColumn('slug', 'text', (col) => col.notNull().unique())
.addColumn('excerpt', 'text', (col) => col.notNull())
.addColumn('content', 'text', (col) => col.notNull())
.addColumn('image', 'text') // nullable by default
.addColumn('version', 'integer', (col) => col.notNull().defaultTo(1))
.addColumn('status', 'text', (col) => col.notNull().defaultTo('DRAFT'))
.addColumn('createdAt', 'timestamptz', (col) => col.notNull().defaultTo(sql`now()`))
.addColumn('updatedAt', 'timestamptz', (col) => col.notNull().defaultTo(sql`now()`))
.execute()
// Add indexes for Post table
await db.schema
.createIndex('post_author_id_idx')
.on('Post')
.column('authorId')
.execute()
await db.schema
.createIndex('post_status_updated_at_idx')
.on('Post')
.columns(['status', 'updatedAt'])
.execute()
// Create Tag table
await db.schema
.createTable('Tag')
.addColumn('id', 'uuid', (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`))
.addColumn('defaultLocale', 'text', (col) => col.notNull().defaultTo('en'))
.addColumn('name', 'text', (col) => col.notNull())
.addColumn('slug', 'text', (col) => col.notNull().unique())
.addColumn('createdAt', 'timestamptz', (col) => col.notNull().defaultTo(sql`now()`))
.addColumn('updatedAt', 'timestamptz', (col) => col.notNull().defaultTo(sql`now()`))
.execute()
// Create PostI18n table
await db.schema
.createTable('PostI18n')
.addColumn('id', 'uuid', (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`))
.addColumn('postId', 'uuid', (col) =>
col.notNull().references('Post.id').onDelete('cascade')
)
.addColumn('locale', 'text', (col) => col.notNull())
.addColumn('title', 'text', (col) => col.notNull())
.addColumn('slug', 'text', (col) => col.notNull())
.addColumn('excerpt', 'text', (col) => col.notNull())
.addColumn('content', 'text', (col) => col.notNull())
.execute()
// Add unique indexes for PostI18n
await db.schema
.createIndex('post_i18n_locale_idx')
.on('PostI18n')
.column('locale')
.execute()
await db.schema
.createIndex('post_i18n_post_id_locale_unique')
.on('PostI18n')
.columns(['postId', 'locale'])
.unique()
.execute()
await db.schema
.createIndex('post_i18n_locale_slug_unique')
.on('PostI18n')
.columns(['locale', 'slug'])
.unique()
.execute()
// Create TagI18n table
await db.schema
.createTable('TagI18n')
.addColumn('id', 'uuid', (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`))
.addColumn('tagId', 'uuid', (col) =>
col.notNull().references('Tag.id').onDelete('cascade')
)
.addColumn('locale', 'text', (col) => col.notNull())
.addColumn('name', 'text', (col) => col.notNull())
.addColumn('slug', 'text', (col) => col.notNull())
.execute()
// Add unique indexes for TagI18n
await db.schema
.createIndex('tag_i18n_locale_idx')
.on('TagI18n')
.column('locale')
.execute()
await db.schema
.createIndex('tag_i18n_tag_id_locale_unique')
.on('TagI18n')
.columns(['tagId', 'locale'])
.unique()
.execute()
await db.schema
.createIndex('tag_i18n_locale_slug_unique')
.on('TagI18n')
.columns(['locale', 'slug'])
.unique()
.execute()
// Create PostTag junction table
await db.schema
.createTable('PostTag')
.addColumn('postId', 'uuid', (col) =>
col.notNull().references('Post.id').onDelete('cascade')
)
.addColumn('tagId', 'uuid', (col) =>
col.notNull().references('Tag.id').onDelete('cascade')
)
.addPrimaryKeyConstraint('pk_post_tag', ['postId', 'tagId'])
.execute()
// Add index for PostTag
await db.schema
.createIndex('post_tag_tag_id_idx')
.on('PostTag')
.column('tagId')
.execute()
}
export async function down(db: Kysely<any>): Promise<void> {
// Drop tables in reverse order to avoid foreign key conflicts
await db.schema.dropTable('PostTag').execute()
await db.schema.dropTable('TagI18n').execute()
await db.schema.dropTable('PostI18n').execute()
await db.schema.dropTable('Tag').execute()
await db.schema.dropTable('Post').execute()
}
For more information about creating and running migrations with Kysely, refer to the Kysely migrations documentation.