Type-safety with TypeScript ORMs and query builders #8
Replies: 10 comments 22 replies
-
Nice article. I wrote quite a bunch with drizzle and kysely over the past 10 months or so, and today I would always pick Kysely over Drizzle. |
Beta Was this translation helpful? Give feedback.
-
@thetutlage, thanks for the write up. Interesting comparison. One thing I would like to see that I don't think Knex supports and I feel that Kysely does better is producing good JSON. For example, from my experience with Knex it always produces a flat result JSON. So if you have two tables e.g., blogs and posts and you want to retrieve a blog with posts, Knex will return duplicated data from the result essentially mapping the query result table after the join to the json object whereas Kysely will allow you to easily configure the right object hierarchy, for example with Kysely: {
"blogTitle": "some blog",
"posts": [
{
"title": "post title"
},
{
"title": "second post title"
}
]
} with Knex: [
{
"blogTitle": "some blog",
"postTitle": "post title"
},
{
"blogTitle": "some blog",
"postTitle": "second post title"
}
] With Knex it seems that afterwards you need to map it yourself to the JSON object structure that you need either manually or by using a naming convention with dots like Sequelize does under the hood with https://github.com/mickhansen/dottie.js. |
Beta Was this translation helpful? Give feedback.
-
There's another library that is also a great typesafe query builder:
But it's usually not known off. It uses references like drizzle. |
Beta Was this translation helpful? Give feedback.
This comment was marked as off-topic.
This comment was marked as off-topic.
-
https://www.prisma.io/ |
Beta Was this translation helpful? Give feedback.
-
Prisma introduced It's intended for query that are better written as raw SQL code (rather than prisma API) with prisma generating correct ts types at the end. https://www.prisma.io/blog/announcing-typedsql-make-your-raw-sql-queries-type-safe-with-prisma-orm |
Beta Was this translation helpful? Give feedback.
-
I'd recommend checking out pg-nano if you're a Postgres user. It's a tool I'm building that generates TypeScript definitions for your Postgres UDFs. So you write your queries in PL/pgSQL or another supported language (i.e. PL/rust) as |
Beta Was this translation helpful? Give feedback.
-
Have you considered orange-orm const rows = await db.books.getAll({
where: x => x.lendings.exists(),
lendings: true
}); |
Beta Was this translation helpful? Give feedback.
-
I'm looking for an ORM that can support branded ids. They all talk about type-safety but none of them is preventing me from using a "InvoiceId" instead of "OrderId" in my queries. |
Beta Was this translation helpful? Give feedback.
-
I'm in the process of finding the best ORM for my use case, however reading through everything here, it doesn't look like there is one. It sounds like I want to use Kysely for the best Type-safety, however it isn't an ORM and misses a lot of things, e.g. relations, migrations, etc. Is there any fully type-safe ORM or any ORM built on top of Kysely which supports multiple database engines, e.g. Postgres, SQlite, etc.? |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
-
Alright, so I maintain a JavaScript ORM called Lucid. Lucid is built on top of knex, which isn't type-safe, and hence, Lucid isn't type-safe either.
My gut feeling is that utilizing SQL to its full potential and still being able to keep the TypeScript API end-to-end type-safe is a very, very, very hard task. Having hooks, an extensible query builder, and integrating ORM/query-builder (in a generic manner) to other parts of the codebase makes things even more difficult.
I am very happy with the current state of Lucid, except its not being type-safe. So, I decided to try Kysely (it's not an ORM) and Drizzle (it's an ORM) and see how it feels to use them.
Before we start, let me say a few words about type safety.
The Test
I was reading the Ecto docs and found a query that involves sub-queries and a few joins. So, I picked that example and reproduced it using Knex, Kysely, and Drizzle. Here's what the SQL query looks like.
We query
lendings
,books
and theusers
table to get a list of books and the name of the last lender for the book.Here's what the SQL tables look like.
The queries
I have written the following queries using Knex, Kysely, and Drizzle.
With Knex
I find Knex syntax is terse and reads more like SQL. Not only the function names but also the flow in which you construct the query are similar to writing SQL.
However, you are free to have typos and incorrect table references, and no one will stop you except for the runtime exceptions.
With Kysely
Kysely, on the other hand, re-orders the writing flow of the query because it strives to keep the query type-safe.
For example, in the following query, you cannot write
db.select
before you write all the joins. This is because the columns from the joins are not visible to theselect
method if it is invoked first.Now, there is nothing wrong with this approach. It seems logical from the TypeScript point of view.
However, when I first started using Kysely, I struggled a bit because I am used to writing
SELECT
statements first out of habit.With Drizzle
Drizzle is Knex trying to be Kysely. First, there is no type-safety in Drizzle when constructing the query.
They use references everywhere to avoid typos and have a single source of truth for all the tables and their columns. However, you can still use the wrong references, and the compiler won't yell at you. ( We talk about more about Drizzle pitfalls in a second )
Since they are using references everywhere, aliasing tables is painful. I have to create a JavaScript variable and use that in the rest of the query. Aliasing tables is a common practice, so I suspect every code path in my application will have a few variable declarations just for that.
Drizzle pitfalls
Initially, I thought the drill of reproducing a SQL query with Drizzle, Kysely, and Knex would take only a few minutes. However, I found some interesting bits about Drizzle and thought I would present them properly in this article.
A simple example
Let's take the simplest example and see if Drizzle can statically validate it for us.
The following query tries to select the
name
column from theusers
table. However, I forgot to write the join in the first place. I expect a type-safe query builder to stop me from writing and running this code.On the other hand, Kysely does precisely what I expected.
Easy to mess up with subqueries
Alright, the above example might be too simple for any sane developer to mess up with. However, with the following query, I myself caught the error at runtime.
Did you spot the error?
On the last line, i.e.,
leftJoin
, since I am joining on a sub-query aliased asl
, theON
clause should referencel.book_id
and notlendings.book_id
.The following is the correct version.
Once again, Kysely catches the error.
Conclusion
Knex is a battle-tested old wizard that allows you to use the full potential of SQL. This might not be a huge deal for simple CRUD apps, but I appreciate knex when writing complex SQL queries.
Kysely is pretty cool. They have pushed the limits as much as they can. I have nothing else to say.
Drizzle gives the impression of type-safety. However, only the query results have type information. You can write invalid queries with Drizzle, just like Knex.\
The references approach has its upsides and downsides. The upside is that you do not have static strings in your codebase. However, the references make the aliasing of tables (as per my limited testing) verbose.
My idea was not to bash Drizzle at all. I wanted to experience both Kysely and Drizzle. It turns out I had different expectations from Drizzle.
If you ask me, I will still pick Knex over Drizzle as of today because the maturity and simplicity of Knex outweigh the typed results of Drizzle for me. For you, it might be the other way around.
Beta Was this translation helpful? Give feedback.
All reactions