Kysely Module
Overview
The kysely module is a developer and admin utility that exposes the database migration and seed history via a REST API. It allows engineers to inspect which Kysely migrations and seeds have been applied to a given environment without direct database access.
Architecture
The module follows the hexagonal (ports & adapters) architecture used across the backend:
kysely/
├── kysely.module.ts # NestJS module — wires DI
├── application/
│ ├── kysely.service.ts # Use cases: getMigrations, getSeeds
│ └── kysely.service.spec.ts
├── domain/
│ └── kysely-repository.domain.ts # IKyselyRepository port + KYSELY_REPOSITORY token
├── infrastructure/
│ └── kysely.repository.ts # Kysely DB adapter (reads views)
└── interfaces/
├── kysely.controller.ts # HTTP endpoints
└── kysely.controller.spec.ts
Layer responsibilities
| Layer | File | Responsibility |
|---|---|---|
| Domain | kysely-repository.domain.ts | Defines the IKyselyRepository port and the KYSELY_REPOSITORY injection token |
| Application | kysely.service.ts | Orchestrates use cases; depends only on IKyselyRepository |
| Infrastructure | kysely.repository.ts | Implements IKyselyRepository using Kysely queries against two DB views |
| Interface | kysely.controller.ts | Thin HTTP controller; maps routes to service methods, handles errors |
Dependency wiring
KyselyController → KyselyService → IKyselyRepository ← KyselyRepository
↑
KYSELY_REPOSITORY token (module provider)
Domain Concepts
- Migration — A schema change file managed by Kysely's migrator. Stored in
kysely_migrationtable; exposed askyselyMigrationView. - Seed — A data seeding script managed by Kysely's seeder. Stored in
kysely_seedstable; exposed askyselySeedsView.
Both records contain only { name: string; timestamp: string }.
API Endpoints
Base path: /kysely
All endpoints require:
- A valid Firebase ID token (
Authorization: Bearer <token>orflowpos-id-tokencookie) - The
Kyselypermission resource role on the authenticated user
GET /kysely/migrations
Returns all applied migrations ordered by timestamp descending.
Response 200:
[
{ "name": "20240201000000_add_locations", "timestamp": "2024-02-01T00:00:00.000Z" },
{ "name": "20240101000000_initial_schema", "timestamp": "2024-01-01T00:00:00.000Z" }
]
GET /kysely/seeds
Returns all applied seeds ordered by timestamp descending.
Response 200:
[
{ "name": "seed_products", "timestamp": "2024-03-15T00:00:00.000Z" },
{ "name": "seed_businesses", "timestamp": "2024-03-01T00:00:00.000Z" }
]
Error responses (both endpoints)
| Status | Meaning |
|---|---|
401 | Missing or invalid Firebase token |
403 | User lacks the Kysely permission |
500 | Database error |
Database Views
The module reads from two PostgreSQL views rather than the underlying tables directly:
| View | Underlying table | Purpose |
|---|---|---|
kyselyMigrationView | kysely_migration | Applied schema migrations |
kyselySeedsView | kysely_seeds | Applied seed runs |
Both views select name and timestamp columns.
Design Decisions
Why expose migrations and seeds via HTTP? Allows platform engineers and admins to verify environment state (e.g., "did staging apply migration X?") without needing database credentials or SSH access.
Why two separate views instead of querying tables directly? Views abstract the underlying Kysely-internal table structure. If Kysely changes its internal schema, the view contract is the stable adapter boundary — the module code does not need to change.
Why KYSELY_REPOSITORY injection token?
Following strict hexagonal architecture, the application layer (KyselyService) depends on the IKyselyRepository interface (the port), not the concrete KyselyRepository class. The token allows NestJS DI to resolve the concrete class at runtime while keeping the service fully decoupled from the infrastructure adapter. This makes unit tests simpler — mock the interface, not the class.
Related
- Database views:
packages/backend/database/src/types/database.types.ts—KyselyMigrationView,KyselySeedsView - Migrations folder:
packages/backend/database/src/migrations/ - Seeds folder:
packages/backend/database/src/seeds/ - Database module:
apps/backend/src/database/ - Connection pool documentation:
docs/database/connection-pool-management.md