Preface: On Honesty
This article is not marketing material. It does not attempt to convince you that Kysera is the best solution for all tasks. Instead, it presents a critical analysis: what problems led to the creation of Kysera, what place it occupies among existing solutions, and for which cases it is actually suitable.
If after reading this you decide that Kysera is not for you — that is a normal and possibly correct conclusion.
The Landscape of Problems
Developer Pain Points in Large Projects
Working with databases in production systems comes with recurring problems that are independent of the chosen tool:
Architectural Problems:
- Transactions distributed across multiple services or layers
- Soft delete that needs to be applied everywhere but is easy to forget
- Audit trail requiring uniformity across all write operations
- Row-Level Security for multi-tenant applications
- Graceful shutdown and connection pool management
Operational Problems:
- Database errors that are hard to understand without knowing PostgreSQL/MySQL codes
- Health checks and connection state monitoring
- Slow queries that need to be found and profiled
- Testing with transaction isolation
Typing Problems:
-
Generated<T>fields that exist on read but not on write - Validation schemas for create/update that differ from the entity type
- Error types that get lost in
unknown
How Different Tools Solve These Problems
Prisma
Approach: Full abstraction. Prisma hides SQL behind a declarative schema and generated client.
Strengths:
- Excellent DX for simple CRUD operations
- Automatic migrations from schema
- Built-in relations and nested queries
- Type-safe client out of the box
Weaknesses:
- Generated code is a black box
- Complex queries require
$queryRaw, losing typing - Limited control over SQL
- Soft delete requires middleware, which is not obvious
- Query engine as a separate process (or WASM) adds overhead
- Migrations are tied to Prisma, difficult to exit
Philosophy: "We know better how to work with databases. Trust the abstraction."
TypeORM
Approach: Classic ORM with decorators, Active Record or Data Mapper.
Strengths:
- Familiar patterns for developers from the Java/C# world
- Support for multiple databases
- Entity lifecycle management
- Query Builder for complex cases
Weaknesses:
- Decorators and metadata are runtime magic
- Identity Map creates non-obvious state
- Lazy loading is a source of N+1 problems
- Typing is often lost in complex queries
- Migrations require synchronization with entities
Philosophy: "ORM should manage the lifecycle of entities."
Drizzle
Approach: Type-safe schema-first query builder with ORM-like API.
Strengths:
- Excellent typing from schema
- SQL is close to the surface
- Lightweight (no runtime engine)
- Relational queries for simple JOINs
Weaknesses:
- Schema in code, not in the database (requires synchronization)
- Smaller ecosystem/plugins than Prisma
- Relational queries are not full-fledged relations
- Complex queries still need raw SQL
Philosophy: "Type safety matters more than abstraction. SQL is not the enemy."
Knex
Approach: Query builder without ORM pretensions.
Strengths:
- Simple and clear API
- Migrations as code
- Support for multiple dialects
- Minimal overhead
Weaknesses:
- Weak typing (from the pre-TypeScript-first era)
- No validation
- No plugin system
- Every project writes its own utilities
Philosophy: "A query builder is all you need."
Kysely
Approach: Type-safe query builder for TypeScript.
Strengths:
- Full query typing
- SQL as a first-class citizen
- Zero runtime overhead
- Predictable SQL output
Weaknesses:
- Only a query builder — no data access patterns
- No validation
- No plugin system
- No utilities for production (health, retry, pagination)
- Every project implements soft delete differently
Philosophy: "Type-safe SQL. Nothing more, nothing less."
Kysera's Position
What Kysera Does NOT Attempt to Do
Before talking about what Kysera does, it's important to understand what it deliberately avoids:
Does not replace Kysely. Kysera is a layer on top of Kysely, not an alternative. You can always use Kysely directly.
Is not an ORM. No entity mapping, Unit of Work, Identity Map, lazy loading. These patterns are a source of complexity that Kysera rejects.
Does not hide SQL. Queries are written in Kysely, SQL remains visible and predictable.
Does not impose architecture. Repository or DAL — the choice is yours. You can use both.
Does not solve all problems. Kysera focuses on specific pain points, not on trying to be "everything for everyone."
What Kysera Does
Kysera provides a toolkit for solving recurring problems in database work:
Plugin System with Query Interception
Instead of remembering about soft delete in every query:
// Without Kysera — easy to forget
const users = await db
.selectFrom('users')
.selectAll()
.where('deleted_at', 'is', null) // Forgot? You'll get deleted users
.execute();
// With Kysera — applied automatically
const executor = await createExecutor(db, [softDeletePlugin()]);
const users = await executor
.selectFrom('users')
.selectAll()
.execute(); // WHERE deleted_at IS NULL added automatically
This works through Proxy-based interception — the same approach used by middleware in Express or interceptors in Axios.
Dual API: Repository and Functional DAL
Repository — for structured CRUD with validation:
const userRepo = orm.createRepository(createUserRepository);
const user = await userRepo.create({
email: 'test@example.com',
name: 'Test'
});
// Validation, typing, plugins — all out of the box
Functional DAL — for complex queries with composition:
const getUserWithStats = createQuery(async (ctx, id: number) => {
const user = await ctx.db
.selectFrom('users')
.selectAll()
.where('id', '=', id)
.executeTakeFirst();
const stats = await ctx.db
.selectFrom('events')
.select(sql<number>`count(*)`.as('total'))
.where('user_id', '=', id)
.executeTakeFirst();
return { user, stats };
});
Typed Error Handling
Instead of parsing error strings:
// Without Kysera
try {
await db.insertInto('users').values(data).execute();
} catch (error: unknown) {
// What is this? UniqueConstraint? ForeignKey? Who knows?
if (error.message.includes('duplicate')) { /* ... */ }
}
// With Kysera
try {
await userRepo.create(data);
} catch (error) {
const dbError = parseDatabaseError(error, 'postgres');
if (dbError instanceof UniqueConstraintError) {
// Typed error with constraint name, table, columns
}
}
Production Utilities
Opt-in packages for production:
// Health checks
const health = await checkDatabaseHealth(db, pool);
// { status: 'healthy', latency: 12, poolMetrics: {...} }
// Retry with exponential backoff
const result = await withRetry(
() => userRepo.findById(id),
{ maxAttempts: 3, backoff: 'exponential' }
);
// Graceful shutdown
await gracefulShutdown(db, {
timeout: 30000,
onClose: () => console.log('DB connections closed')
});
Architectural Decision: Unified Execution Layer
The key architectural decision in Kysera is KyseraExecutor. It's a Proxy wrapper over Kysely that:
-
Intercepts
selectFrom,insertInto,updateTable,deleteFrom - Applies plugins to each query
- Propagates plugins into transactions
- Maintains zero overhead when no plugins are present
┌─────────────────────────────────────────┐
│ Application Code │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ KyseraExecutor │ ← Proxy intercepts │
│ │ (selectFrom) │ query methods │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Plugin Chain │ ← Plugins modify │
│ │ (interceptors) │ query builders │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────┐ │
│ │ Kysely │ ← Original QB │
│ └────────┬────────┘ │
│ │ │
│ ▼ │
│ Database │
└─────────────────────────────────────────┘
This allows plugins to work with both patterns — Repository and DAL — without code duplication.
Honest Comparison
Kysera vs Prisma
| Aspect | Prisma | Kysera |
|---|---|---|
| Abstraction | High (hides SQL) | Low (SQL visible) |
| Typing | Generated from schema | From Kysely + DB schema |
| Soft delete | Middleware (not obvious) | Plugin (explicit) |
| Complex queries |
$queryRaw (loses types) |
Kysely (preserves types) |
| Bundle size | ~2MB (with engine) | ~30KB (all packages) |
| Lock-in | High | Low (can fall back to Kysely) |
When Prisma is better:
- Quick start with simple CRUD
- Team doesn't want to think about SQL
- Need automatic migrations from schema
When Kysera is better:
- Complex queries make up a significant part of the project
- SQL control is important
- Bundle size is critical
- Need gradual migration or exit capability
Kysera vs Drizzle
| Aspect | Drizzle | Kysera |
|---|---|---|
| Schema source | Code (schema.ts) | Database |
| Plugin system | No | Yes |
| Repository pattern | No | Optional |
| Error handling | Generic | Typed errors |
| Production utils | Partial | Full set (opt-in) |
When Drizzle is better:
- You prefer schema-first approach
- Relational queries cover your JOINs
- You don't need plugins
When Kysera is better:
- Schema in DB (database-first)
- Need soft delete, audit, RLS as plugins
- Production utilities are important
Kysera vs "Just Kysely"
This is the most important question: why Kysera if Kysely exists?
| Problem | Kysely | Kysely + Kysera |
|---|---|---|
| Soft delete | Manual WHERE in every query | Plugin automatically |
| Audit trail | Your own implementation | Plugin with full history |
| RLS | Manual filtering | Plugin with AsyncLocalStorage |
| Error parsing |
try/catch + strings |
Typed errors |
| Pagination | Your own implementation | Offset + cursor out of the box |
| Health checks | Your own implementation | @kysera/infra |
| Retry logic | Your own implementation | @kysera/infra |
| Validation | Your own integration | Built-in (Zod, Valibot, etc.) |
| Testing | Your own isolation | Transaction rollback helpers |
Conclusion: Kysera is a codification of best practices that every team otherwise writes from scratch.
A Critical Look at Kysera
Weaknesses
1. Additional Abstraction
Every layer of abstraction is:
- A potential place for bugs
- A need to study documentation
- A dependency on maintainers
For simple projects, the overhead may not pay off.
2. Smaller Ecosystem Than Prisma
Prisma has a huge ecosystem: Prisma Studio, Prisma Accelerate, integrations with all frameworks. Kysera is a young project with a smaller community.
3. Two Patterns — Potential Confusion
Repository and DAL in the same project can create inconsistency if the team doesn't agree on conventions.
4. Plugin Dependencies
Plugins have execution order and dependencies. Wrong order can lead to unexpected behavior.
When NOT to Use Kysera
Simple CRUD project — Prisma or even raw Kysely will be simpler.
Team doesn't know SQL — Kysera doesn't hide SQL, this is a deliberate choice.
Need entity lifecycle — Kysera rejects these patterns. If you need Identity Map or Unit of Work — look at TypeORM.
Maximum DX matters more than control — Prisma gives better out-of-the-box experience for simple cases.
You already have a working solution — Migration for migration's sake doesn't make sense.
Who is Kysera For?
The "Ideal" User Profile
Kysera is created for developers who:
- Understand SQL and want to control it
- Work on production systems with reliability requirements
- Value explicitness over magic
- Prefer composition over inheritance
- Want opt-in functionality, not "all-inclusive"
- Use TypeScript with strict mode
Typical Scenarios
1. Multi-tenant SaaS
RLS plugin + AsyncLocalStorage for automatic filtering by tenant_id.
2. Applications with Audit Requirements
Audit plugin for full change history with restore capability.
3. Complex Domain-Driven Applications
Functional DAL for complex queries + Repository for CRUD.
4. Microservices with Shared Database Patterns
Plugins as reusable packages between services.
The Philosophy of "Start minimal, grow as needed, stay transparent"
Start Minimal
Minimal installation — only @kysera/core (8KB):
npm install kysely @kysera/core
You get error handling and pagination. Everything else is opt-in.
Grow as Needed
As your project grows, add what you need:
# Need soft delete?
npm install @kysera/soft-delete
# Need audit?
npm install @kysera/audit
# Need health checks?
npm install @kysera/infra
Stay Transparent
Every operation:
- Generates predictable SQL
- Can be debugged via @kysera/debug
- Has no hidden state
- Works in transactions the same as outside them
Conclusion: An Honest Assessment
Kysera is not a revolution in data access. It is an evolutionary solution to specific problems that arise when scaling TypeScript projects with relational databases.
Strengths:
- Solves real production problems (soft delete, audit, RLS, error handling)
- Preserves SQL control through Kysely
- Modular architecture — take only what you need
- Low lock-in — can fall back to pure Kysely
- Plugin system for reusing patterns
Weaknesses:
- Additional abstraction over Kysely
- Smaller ecosystem than Prisma
- Requires understanding of SQL
- Young project
Verdict:
If you:
- Already use or are planning to use Kysely
- Work on a production system
- Want plugins for cross-cutting concerns
- Value explicit over implicit
...then Kysera is worth considering.
If you need maximum DX with minimal SQL, or you're working on a simple project — Prisma or Drizzle might be a better fit.
Kysera doesn't try to be the best solution for everyone. It tries to be the right solution for a certain class of problems.
Appendix: Links for Further Reading
- Kysely Documentation — the foundation on which Kysera is built
- Kysera Documentation — quick start in 5 minutes
Top comments (0)