[HN Gopher] Kysely: TypeScript SQL Query Builder ___________________________________________________________________ Kysely: TypeScript SQL Query Builder Author : cal85 Score : 138 points Date : 2023-01-24 17:17 UTC (5 hours ago) (HTM) web link (github.com) (TXT) w3m dump (github.com) | zerace wrote: | I love Kysely! I use it for a couple of production applications | and it works quite well. Type support is better than Knex, and | Kysely is really lightweight so I can use it in my projects | without being concerned about performance issues. | | Really, when you look at options like this, you start to break | them down into 3 distinct categories: | | 1. Raw adapter usage - writing SQL. Performant, but can get | tedious at scale, and weird to add types to. | | 2. Knex/Kysely, lightweight query builders. Readable, composable, | and support types well, but a step removed from the performance | of (1). Some would argue (1) is more universally understandable, | too, although query builders make things easy for those more- | familiar with programming languages than query languages. | | 3. Full-fledged ORMs like TypeORM, Sequelize, Prisma. Often | require much more ecosystem buy-in and come with their own | performance issues and problems. | | I usually choose 1/2 depending on the project to keep things | simple. | | I have pretty much had no issue with it so far. The only thing | that I would call out is that you _must_ run a migration | initially to set things up, or your queries will hang. This has | stumped me a few times (despite being obvious after-the-fact). It | also interfaces really well with postgres, and has nice support | for certain features (like jsonb). | guntars wrote: | I would prefer 1. if it had full type safety. I'm imagining | some sort of a build process that looks for any .sql files in | the project and spits out .sql.d.ts in the same directory with | the input and output types according to the current schema of | the database. Another nice thing about a setup like this imo | would be that the .sql files would have the full support of the | editor for completions and execution, unlike sql fragments in a | Typescript file. | csnweb wrote: | For Postgres there is https://github.com/adelsz/pgtyped, | sounds pretty much like what you describe? | cheeseface wrote: | Slonik comes pretty close https://github.com/gajus/slonik. It | does not inspect your DB schema, but has worked very well in | our SQL-heavy architecture. | kristiandupont wrote: | I made a tool that generates Typescript types out of a live | Postgres database. I've had a request for Kysely support | (https://github.com/kristiandupont/kanel/issues/273), but I | more or less forgot about it. I would love to hear if that | would be helpful. | LewisJEllis wrote: | I think https://github.com/RobinBlomberg/kysely-codegen does | roughly the same thing? | kristiandupont wrote: | Looks like it, yes. Do you use it? | mirekrusin wrote: | We use in prod variant of no 1. [0]. Why? Because: | | * it's sql | | * it's extremely lightweight (built on pure, functional | combinators) | | * it allows us to use more complex patterns ie. convention | where every json field ends with Json which is automatically | parsed; which, unlike datatype alone, allows us to create | composable query to fetch arbitrarily nested graphs and | promoting single [$] key ie. to return list of emails as | `string[]` not `{ email: string }[]` with `select email as [$] | from Users` etc. | | * has convenience combinators for things like constructing | where clauses from monodb like queries | | * all usual queries like CRUD, exists etc. and some more | complex sql-wise but simple function-api-wise ie. insertIgnore | list of objects, merge1n, upsert etc all have convenient | function apis and allow for composing whatever more is needed | for the project | | We resort to runtime type assertions [1] which works well for | this and all other i/o; runtime type assertions are necessary | for cases when your running service is incorrectly attached to | old or future remote schema/api (there are other protections | against it but still happens). | | [0] https://github.com/appliedblockchain/tsql | | [1] https://github.com/appliedblockchain/assert-combinators | robertn702 wrote: | You might also want to consider pgTyped | (https://github.com/adelsz/pgtyped). It's supposed to make SQL | and TS work together. I haven't gotten around to using it yet | but I hear good things. | drpotato wrote: | Can weigh in here, we use pgtyped heavily at work and it's | really good. There are some constraints with the type | inference on complex queries, but it's a decent trade off | IMO. | HatchedLake721 wrote: | Koskimas, awesome work! Will we ever see a lightweight ORM like | Objection.js on top of Kysely? | LewisJEllis wrote: | No, he's stated somewhere in the issue tracker that he has no | plans to ever do this. | | edit: here - | https://github.com/koskimas/kysely/issues/162#issuecomment-1... | satvikpendem wrote: | Reminds me of Prisma in its type safety, any major differences? I | see that it's a query builder but when I used them in the past, | they honestly didn't feel _that_ different to ORMs. | | In Rust, there is sqlx which lets you write SQL but checks at | compile time whether the SQL is valid for the database, by | connecting to the database, performing the transaction then | rolling back, picking up and displaying any errors along the way. | | Now with Prisma, I like it since it provides one unified database | schema that I can commit into git (which avoids the problem of | overlapping migrations from team members simultaneously working | on separate branches that then need to be merged back in; with a | git compatible schema, we _must_ handle merge conflicts) and be | able to transport across languages. I recently ported a | TypeScript project to Rust and the data layer at least was very | easy due to this. I used Prisma Client Rust for it, which is the | beauty of having a language agnostic API, you can generate your | own clients in whatever language you want. | skrebbel wrote: | Also check out https://jawj.github.io/zapatos/ which has a | similar non-ORM yet fully typesafe approach. | CuriouslyC wrote: | Notably, in Zapatos you write SQL, whereas with this you use a | query builder. Personally I think staying close to SQL is a | feature, but YMMV. | gmac wrote: | Yes -- though there are also shortcut functions for basic | CRUD (and also for lateral joins) which give you automatic | typing. | yasserf wrote: | This is really cool, will look into using it in future projects! | | I also made a tool (https://github.com/vramework/schemats) that | generates the types directly from the db, which means whenever | you do a DB migration your database types automatically update. | Was forked from the original schemats library a couple years ago. | | I also created a lightweight library ontop of pg that is less of | a query builder and more of a typed CRUD + SQL for non trivial | queries (https://github.com/vramework/postgres-typed). Most | queries I deal with in a day to day is usually crud so I find it | a little easier, but it's much less powerful then Kysely! I fall | more into the camp of writing complex queries in SQL with small | helpers and writing simple ones with util functions and | typescript. | | Edit: Will be looking into cleaning up docs and tests next month. | Right now everything is in the ReadMe and examples | crdrost wrote: | What a beautiful api. As a Knex user I appreciate the design | influence of Knex, which similarly does not try to give you a | full orm but just a structured builder for SQL queries. But I | agree that Knex was always a little weird in that it only would | execute the query if it was being listened for (so | `query.getSql()` or something would get you the stringified | query, while `await query` would actually execute it) and had | some other quirks (in particular different ways of specifying the | root table's name at the position in the FROM depending on what | you were doing). | | The TypeScript integration is nice too, I also have treated TS | this way as "programmable autocomplete for VS Code." I will say | that doesn't make it super maintainable usually but that's not an | issue for the 0.x.x releases of course. | jakewins wrote: | Dang, how do they implement the "parse text strings and generate | types immediately available in auto-complete" thing?? I can see | how you could do that with Rust Macros, but how do they do it in | TS? | | Eg. this thing: | | blah.select(['pet.name as pet_name']) | | is inferred to return a type with a `pet_name` field, parsing the | contents of the SQL expression? | | [Update]: whatafak lol TS has way more juice in it than I | realized: | https://github.com/koskimas/kysely/blob/master/src/parser/se... | nikeee wrote: | You can use these template literal types + infer to build an | entire SQL parser. I did a POC that infers SQL query types by | parsing the SQL query on a type level: | | https://github.com/nikeee/sequelts | | Building this parser is pretty cumbersome and supporting | multiple SQL dialects would be lots of pain. While I'm not a | fan of query builders per se, Kysely pretty much covers | everything that my POC tried to cover (except that 0 runtime | overhead). However, you get the option to use different DBMs in | tests than in production (pg in prod, sqlite in tests), which | is a huge benefit for a lot of people. sequelts was designed to | work with sqlite only. And it's a hack. | kamilafsar wrote: | The relevant TS feature is called template literal types: | | https://www.typescriptlang.org/docs/handbook/2/template-lite... | haywirez wrote: | Objection is an incredible SQL ORM library from the same author, | I learned a lot of concepts from it over the years. | bottlepalm wrote: | I'm still hoping for a real ORM for JavaScript like Entity | Framework. I find that devs who haven't used EF don't really | understand the true power of an ORM. | [deleted] | [deleted] | MuffinFlavored wrote: | How does HN receive SQL builders in general? I feel like most of | us agree ORMs are typically a bad idea. I feel like that almost | instantly leaves the need for "something" to take its place. In | my experience, it's typically been a query builder like this. | | I've also tried: | | https://knexjs.org/ | | https://www.npmjs.com/package/sql-template-strings ("out of date" | since like 2016? https://www.npmjs.com/package/sql-template-tag | might be better) | | Are query builders an anti pattern? People who are doing | serious/logic heavy stuff with SQL, how do you avoid a query | builder (if at all?) | giraffe_lady wrote: | I think you'll see the whole range of responses, even some very | reasonable defenses of ORMs for some cases. | | I've come to prefer a yesql type approach that parses your sql | into functions and provides a mechanism for applying functions | to the bound data before running and the returned data after. | Keeps things nicely separated and you can unit test your sql | functions as application code. | nullwarp wrote: | I'm a big fan of them over traditional ORMs and use knex in | quite a few projects at this point. I find them a really good | balance between full on ORM and just writing out pure SQL | queries. | | Definitely going to give Kysely a try on my next project | [deleted] | crdrost wrote: | There's nothing wrong with query builders, no antipattern. | | In many ways it's similar to preferring C where you can really | feel the assembly-language underneath you even if you're not | writing it. | | If you have written enough raw SQL you do a similar thing by | convention... So for example if you look at my raw SQL queries | you will notice that I usually only select specific columns | with one-letter or two-letter table prefixes for disambiguation | (because I hate "adding a column" being a breaking change! ... | I am flexible on the name size but I like the freedom to make | my table names long and expressive if feasible, group them with | prefixes that relate related tables, etc). Then in the FROM, I | only use JOIN and LEFT JOIN unless there's _really_ no other | way, and all my inner JOINs come before my left ones. All of | those have AS statements renaming them to one-character | prefixes too, and they have a clear ON condition that connects | them to the above blob (so always "AS s WHERE s.whatever = | ...") even though that makes the queries longer to refactor | when you want to rearrange the joins (you often have to shift | all the OFs down by one and reverse which side of the equality | comes first or some nonsense). Subqueries should move up to a | WITH or should be rewritten as LEFT JOINs if feasible. | | You want to use structure to guide a reader through this thing | that could be complicated... That structure could be lexical | structure in the SQL itself or it could be syntactic structure | from a wrapping language, I don't care so much. The real | problem is having one source-of-truth for the database schema, | and that problem is just barely tractable with current | languages, but I don't see anybody who does it right. | jerryu wrote: | Very cool! | | Not sure if you use a diagram tool to visualize your databases | but I built ERD Lab specially for developers and would love to | get your feedback. | | If you are on desktop/laptop you can login as guest. No | registration required. | | Here is a 1 minute video of ERDLab in action. | https://www.youtube.com/watch?v=9VaBRPAtX08 | | What do you think about creating diagrams using the simple markup | language in my tool? | bastardoperator wrote: | How does this compare to say Prisma? I want to write SQL more | than I want to write Javascript. I got really hung up on writing | joins with Prisma and don't want to use a raw query. How would | this compare assuming they're comparable, thanks in advance. | cypress66 wrote: | As someone who uses both prisma and knex (so something like | kysely): | | Prisma is very very nice, it has maybe the best DX out there. | My issue with it is performance. It is much slower than query | builders or raw sql. It's also a huge black box, although I | haven't had any issues, you're dealing with a complex beast. | | Knex (and other query builders) is nicer than raw sql, has good | performance, and it's fairly transparent (it's not a 800 pound | gorilla like Prisma) | LewisJEllis wrote: | I see a kysely + kysely-codegen (generates types from DB | schema) setup as comparable to Prisma in TS integration, with | the added flexibility/closeness-to-SQL of the querybuilder. | | If you: | | - have used/liked Knex (or similar querybuilders) before | | - like the TS integration + type safety of Prisma | | - but find Prisma to be a bit too magic/heavy with its query | engine and schema management | | - and/or just want to be closer to SQL | | then Kysely is what you're looking for. | chetanbhasin wrote: | I think that is particularly what I like about this. With | Prisma optimising your code is very hard because you cannot | just customise joins like here. From the example, it seems that | you can create joins and it also shows you in the documentation | what the actual SQL for that will look like. | | I still have Prisma running on my projects, so it will be a bit | hard to move now particularly because it has TS native | migrations, which is another issue. If I wanted to use these | outside of TypeScript (let's say another service or | middleware), then it would be very hard. | seer wrote: | you can try https://github.com/ivank/potygen - type gen _from_ | sql itself supports all kinds of complex stuff like views, ctes | json objects etc. | dglass wrote: | Prisma is good for writing simple selects, updates, and | deletes. But the moment you need to write any kind of advanced | query with joins, nested queries, unions, etc. it gets | frustrating very quickly. | | Kysely and Knex are far more flexible for writing complex | queries and don't get in your way. ___________________________________________________________________ (page generated 2023-01-24 23:00 UTC)