Skip to main content

Getting Started

Get up and running with Kysera in 5 minutes.

Installation

# Install Kysely (required) and database driver
npm install kysely pg

# Install Kysera packages (pick what you need)
npm install @kysera/core # Errors, pagination, types, logger (~8KB)
npm install @kysera/repository # Repository pattern with validation adapters
npm install @kysera/dal # Functional DAL with type inference

# Optional validation library (choose one or none)
npm install zod # Popular schema validation
# or: npm install valibot # Lightweight alternative
# or: npm install @sinclair/typebox # JSON Schema based

# Infrastructure (opt-in)
npm install @kysera/infra # Health checks, retry, circuit breaker
npm install @kysera/debug # Query logging and profiling
npm install @kysera/testing # Test utilities (dev dependency)

# Plugins
npm install @kysera/soft-delete # Soft delete plugin
npm install @kysera/audit # Audit logging plugin
npm install @kysera/timestamps # Auto timestamps plugin
npm install @kysera/migrations # Migration system

Quick Start

1. Define Your Database Schema

import { Generated } from 'kysely'

interface Database {
users: {
id: Generated<number>
email: string
name: string
created_at: Generated<Date>
}
posts: {
id: Generated<number>
user_id: number
title: string
content: string
created_at: Generated<Date>
}
}

2. Create Database Connection

import { Kysely, PostgresDialect } from 'kysely'
import { Pool } from 'pg'

const db = new Kysely<Database>({
dialect: new PostgresDialect({
pool: new Pool({
host: 'localhost',
database: 'myapp',
user: 'postgres',
password: 'postgres',
max: 10
})
})
})

3. Create Repositories

import { createRepositoryFactory } from '@kysera/repository'
import { z } from 'zod'

// Define validation schemas
const userSchema = z.object({
email: z.string().email(),
name: z.string().min(1)
})

// Create factory
const factory = createRepositoryFactory(db)

// Create repository
const userRepo = factory.create({
tableName: 'users' as const,
mapRow: (row) => row,
schemas: {
create: userSchema,
update: userSchema.partial()
}
})

4. Use Repositories

// Create a user
const user = await userRepo.create({
email: 'john@example.com',
name: 'John Doe'
})

// Find user by ID
const foundUser = await userRepo.findById(user.id)

// Update user
const updated = await userRepo.update(user.id, {
name: 'John Smith'
})

// List users with pagination
const { data, hasNext } = await userRepo.findAll({
limit: 10,
offset: 0
})

// Delete user
await userRepo.delete(user.id)

Using Transactions

await db.transaction().execute(async (trx) => {
// Create repositories with transaction executor
const txFactory = createRepositoryFactory(trx)
const txUserRepo = txFactory.create({ /* ... */ })
const txPostRepo = txFactory.create({ /* ... */ })

// All operations are atomic
const user = await txUserRepo.create({
email: 'jane@example.com',
name: 'Jane Doe'
})

await txPostRepo.create({
user_id: user.id,
title: 'First Post',
content: 'Hello World!'
})

// If error occurs, both operations roll back
})

Adding Plugins

Soft Delete

import { createORM } from '@kysera/repository'
import { softDeletePlugin } from '@kysera/soft-delete'

// Note: createORM creates a plugin container, not a traditional ORM
// It has no entity mapping, Unit of Work, or Identity Map
const orm = await createORM(db, [
softDeletePlugin({ deletedAtColumn: 'deleted_at' })
])

const userRepo = orm.createRepository((executor) => {
const factory = createRepositoryFactory(executor)
return factory.create({ tableName: 'users', /* ... */ })
})

// Soft delete (sets deleted_at timestamp)
await userRepo.softDelete(userId)

// Find only non-deleted records (automatic)
const activeUsers = await userRepo.findAll()

// Include deleted records
const allUsers = await userRepo.findAllWithDeleted()

// Restore soft-deleted record
await userRepo.restore(userId)

Audit Logging

import { auditPlugin } from '@kysera/audit'

const orm = await createORM(db, [
auditPlugin({
getUserId: () => currentUser?.id || null,
captureOldValues: true,
captureNewValues: true
})
])

// All CRUD operations are now audited automatically
const user = await userRepo.create({ email: 'test@example.com', name: 'Test' })

// Get audit history
const history = await userRepo.getAuditHistory(user.id)

Timestamps

import { timestampsPlugin } from '@kysera/timestamps'

const orm = await createORM(db, [
timestampsPlugin({
createdAtColumn: 'created_at',
updatedAtColumn: 'updated_at'
})
])

// created_at and updated_at are set automatically
const post = await postRepo.create({
title: 'My Post',
content: 'Content'
})

// updated_at is updated automatically on every update
await postRepo.update(post.id, { title: 'Updated Title' })

Health Checks

import { checkDatabaseHealth, createMetricsPool } from '@kysera/infra'

const pool = new Pool({ /* config */ })
const metricsPool = createMetricsPool(pool)

const health = await checkDatabaseHealth(db, metricsPool)
console.log(health)
// {
// status: 'healthy',
// checks: {
// database: { connected: true, latency: 12 },
// pool: { size: 10, active: 2, idle: 8, waiting: 0 }
// },
// timestamp: Date
// }

Error Handling

import { DatabaseError, UniqueConstraintError, ForeignKeyError, NotFoundError } from '@kysera/core'
import { ZodError } from 'zod'

try {
await userRepo.create({ email: 'duplicate@example.com', name: 'User' })
} catch (error) {
if (error instanceof ZodError) {
// Validation error from Zod schema
console.error('Invalid input:', error.errors)
} else if (error instanceof UniqueConstraintError) {
console.error('Email already exists:', error.constraint)
} else if (error instanceof ForeignKeyError) {
console.error('Referenced record not found:', error.constraint)
} else if (error instanceof NotFoundError) {
console.error('Record not found')
} else if (error instanceof DatabaseError) {
console.error('Database error:', error.code, error.detail)
} else {
throw error
}
}

Pagination

import { paginate, paginateCursor } from '@kysera/core'

// Offset-based pagination
const page1 = await paginate(
db.selectFrom('users').selectAll(),
{ page: 1, limit: 20 }
)

// Cursor-based pagination (more efficient for large datasets)
const result = await paginateCursor(
db.selectFrom('users').selectAll(),
{
orderBy: [{ column: 'created_at', direction: 'desc' }],
limit: 20
}
)

// Get next page using cursor
const nextPage = await paginateCursor(
db.selectFrom('users').selectAll(),
{
orderBy: [{ column: 'created_at', direction: 'desc' }],
limit: 20,
cursor: result.pagination.nextCursor
}
)

Next Steps