Why I Ditched TypeORM for Kysely (And Why You Should Too)
I ditched TypeORM for Kysely and finally understand what my database queries are doing. Less magic, more control, actually type-safe.
I recently made the switch to Kysely, and honestly, I should have done so much sooner. After spending countless hours wrestling with Prisma and TypeORM, discovering Kysely was a genuine relief.
The Problem with Traditional ORMs
Don't get me wrong, I understand why ORMs like TypeORM and Prisma exist. The promise is seductive: abstract away the messy SQL, work with objects instead of queries, and let the magic handle everything. But here's the thing: I'm tired of the magic.
With TypeORM, I constantly found myself in situations where I genuinely didn't understand what was happening:
- When do I need to specify
@JoinColumnin a relation? Sometimes it works without it, sometimes it doesn't. The rules feel arbitrary. - Should I lazy load or eager load this relation? The decision seems simple until you're debugging performance issues three months later.
- When do cascades actually matter? I've set cascade options that did nothing, and forgotten them when they deleted half my database.
It's an abstraction with its own weird, unintuitive rules that you have to learn on top of SQL. You end up needing to know both SQL and the ORM's idiosyncrasies, except now you have less control and less visibility into what's actually happening.
Enter Kysely: The Anti-ORM
Kysely takes a completely different approach. It's a TypeScript query builder, not an ORM. And that distinction matters more than I initially realized.
Here's what I love about it:
1. Less Magic, More Clarity
With Kysely, what you write is what you get. You're writing SQL queries, just with TypeScript's type system backing you up. There's no hidden behavior, no surprise N+1 queries, no mysterious lazy loading gotchas.
typescript
// You write this
const users = await db
.selectFrom('users')
.leftJoin('posts', 'posts.user_id', 'users.id')
.selectAll('users')
.execute()
// And you know EXACTLY what SQL is running2. No Big Setup Bullshit
Remember the Prisma setup? Generate the client, run the migrations, hope the generated types are correct, deal with the monolithic schema file? Or TypeORM's decorators everywhere, the need for reflect-metadata, the elaborate configuration?
Kysely? Install it, point it at your database, and you're done. The types are generated from your actual database schema, not from some intermediate representation that might be out of sync.
3. Migrations That Make Sense
Kysely's migration system is refreshingly simple. You write TypeScript (or SQL if you prefer), and it runs them in order. No magic, no auto-generation that produces garbage you have to manually fix, no praying that the ORM correctly interpreted your schema changes.
4. Actually Type-Safe
TypeORM claims to be type-safe, but let me tell you about the number of times I've gotten runtime errors because the types didn't match reality. Relations that TypeScript insisted existed but were undefined at runtime. Columns that were nullable in the database but not in the types (or vice versa).
Kysely generates types directly from your database schema. If your query is invalid, TypeScript will tell you before you run it. And the types actually reflect reality because they are reality.
But Don't You Miss the ORM Features?
Yes, with Kysely you have to do the ORM part yourself. There's no automatic object mapping, no active record pattern, no repository methods that magically know how to save your entities.
And you know what? I don't miss it at all.
Here's why: in 2025 (and beyond), we have AI coding assistants. Writing the boilerplate to map database rows to objects is trivial now. What used to be the tedious part, writing the same CRUD operations over and over, takes seconds with Copilot or Claude.
What I do get in exchange is:
- Full control over query optimization. I can see exactly what's being selected, joined, and filtered.
- No surprises. I always know what query is running and what data I'm getting back.
- Better performance. Because I'm writing the queries, I can optimize them for my specific use case instead of working around ORM limitations.
- Easier debugging. When something goes wrong, I know exactly where to look because I wrote the query.
Should You Switch?
If you're frustrated by your ORM's magic, if you find yourself fighting with TypeORM's relation system, or if you just want to actually understand what queries your application is running - give Kysely a shot.
You'll write more code. But it'll be your code, and you'll know exactly what it's doing. In a world where understanding your system is increasingly important, that clarity is worth its weight in gold.
Plus, with AI assistants handling the boilerplate, the main downside of query builders has more or less evaporated. What you're left with is a tool that's simple, transparent, and actually type-safe.