September 5th, 2024

Show HN: Node.js ORM to query SQL database through an array-like API

Qustar is a TypeScript library for querying SQL databases, supporting PostgreSQL, SQLite, MySQL, and MariaDB, with a high-level query builder and immutable queries for safe reuse.

Read original articleLink Icon
CuriositySkepticismEnthusiasm
Show HN: Node.js ORM to query SQL database through an array-like API

Qustar is a TypeScript library that facilitates querying SQL databases through an array-like API. It features a high-level query builder that allows users to construct SQL queries in an expressive manner. The library is fully compatible with TypeScript and supports multiple databases, including PostgreSQL, SQLite, MySQL, and MariaDB, with ongoing support for SQL Server and Oracle. Qustar includes navigation properties for easy data traversal, the ability to execute raw SQL queries, and ensures query immutability for safe reuse. To get started with Qustar using PostgreSQL, users can install it via npm and follow a simple example that demonstrates how to define a schema, compose a query, connect to the database, and execute the query. The library offers specific drivers for each supported database, such as `qustar-pg` for PostgreSQL and `qustar-better-sqlite3` for SQLite. Qustar is licensed under the MIT License, and further details can be found on its GitHub repository.

- Qustar is a TypeScript library for querying SQL databases.

- It supports PostgreSQL, SQLite, MySQL, and MariaDB, with SQL Server and Oracle in development.

- The library features a high-level query builder and raw SQL execution.

- Queries are immutable, promoting safe reuse.

- Qustar is licensed under the MIT License.

AI: What people are saying
The comments on the Qustar library reveal a mix of enthusiasm and skepticism regarding its approach to SQL querying in TypeScript.
  • Many users appreciate the TypeScript integration and the potential for type safety and autocomplete features.
  • There are concerns about the effectiveness of abstractions, with some commenters arguing that SQL is valuable and should not be overly abstracted.
  • Comparisons are made to existing ORMs and libraries, such as Drizzle and Entity Framework, with users expressing interest in how Qustar differentiates itself.
  • Some commenters highlight the importance of preventing SQL injection and request more documentation on query parameterization.
  • Overall, there is a desire for a balance between ease of use and the power of raw SQL, with some advocating for a more straightforward approach.
Link Icon 35 comments
By @xonix - 4 months
My take on this is that it's not always the best idea to abstract-out SQL. You see, the SQL itself is too valuable abstraction, and also a very "wide" one. Any attempt to hide it behind another abstraction layer will face these problems:

- need to learn secondary API which still doesn't cover the whole scope of SQL

- abstraction which is guaranteed to leak, because any time you'll need to optimize - you'll need to start reason in terms of SQL and try to force the ORM produce SQL you need.

- performance

- deceptive simplicity, when it's super-easy to start on simple examples, but it's getting increasingly hard as you go. But at the point you realize it doesn't work (well) - you already produced tons of code which business will disallow you to simply rewrite

(knowledge based on my own hard experiences)

By @arnorhs - 4 months
Nice, looks promising. How does this compare to drizzle?

Context:

We've had a lot of ORM frameworks come and go in node.js - sequelize, typeorm etc, but none of them have really caught on.

Things have been changing a lot lately after typescript took over, so we've seen a bunch of ORMs take off that give you a really good typescript experience.

So, the juggernaut in this space is of course prisma, which is super expressive and over all pretty decent - it comes with its own way to define schemas, migrations etc .. so that might not be everybody's cup of tea. (and then there's the larger runtime, that have lambda-users complaining - though that has mostly been addressed now where the binary is much smaller)

So despite it being a pretty opinionated framework really, what it gives you are really rich typescript integrated queries. And all in all it works pretty well - i've been using it at work for about 3 years and I'm just really pleased with it for the most part.

The newcomer in the space that's gaining a lot of traction is Drizzle - where it's mostly a way to define tables and queries - it also gives you really rich typed queries - and it happens all in TS/JS land.

this project of yours reminds of drizzle - kind of similar in a lot of ways.

I'm super interested to understand how this compares to drizzle and which problems with drizzle this attempts to solve

By @efitz - 4 months
Now that we have about 15 years of ORMs, do they really make things easier?

SQL is not a difficult language to learn, and views and stored procedures provide a stable interface that decouples the underlying table schema, allowing for migrations and refactoring of the database structure without having to rewrite a lot of code.

ORMs seem to me to be mostly about syntactic sugar nowadays; I’m worried that the abstractions that they set up insulate the developer from the reality of the system they’re depending on - like any abstraction, they probably work fine right to the very point they don’t work at all.

I’m not complaining about this project; it looks cool and I can see the attraction of staying in a single language paradigm, but I am very wary of abstractions, especially those that hide complex systems behind them.

By @sigseg1v - 4 months
Cool project!

Looking at the docs, for example the pg connector, I couldn't easily find information about how it parameterizes the queries built through method chaining.

For example, if I run

   .filter(user => user.name.eq(unsanitizedInput))
I am presuming that the unsanitizedInput will be put into a parameter? For me, using ORMs on a team that may include juniors, that is one of the key things an ORM provides: the ability to know for sure that a query is immune to SQL injection.

If you had more examples on the connectors of queries like this, and also maybe some larger ones, with the resulting SQL output, I think that might increase adoption.

By @anonzzzies - 4 months
Very nice! Almost everyone I know misses Entityframework if they ever worked with it and similar ergonomic ways in other languages (clojure/cl). Entityframework has it's downsides, but it's so nice to develop with. I don't mind (and often use SQL), in fact, since no longer using C#, I find myself using SQL more often than ORMs as everything is so ... clumsy... compared to entityframework.

Continue doing the excellent work please!

By @EarthLaunch - 4 months
An intriguing idea! I like this approach for being an innovative interface to SQL. I wonder if it would reduce cognitive load when interfacing with the DB.

I'm a game dev and often need to avoid situations where I'm using '.map' to iterate an entire array, for performance reasons. It would feel odd to use the concept, knowing it wasn't really iterating and/or was using an index. Is that how it works?

By @Eric_WVGG - 4 months
I love your syntax for joins and unions!

A bit puzzled by why the connector slots into the query, instead of the query slotting into the connector, given that it’s the connector that’s actually doing the work. I.e. ‘connector.fetch(query)‘ … rather than… ‘query.fetch(connector)‘

By @v_b - 4 months
It is dope, please continue on this.

I used to work with TypeORM and really missed using EntityFramework. That actually led me to switch to Mongo (Mongoose).

I'm really looking forward to this project!

By @wruza - 4 months
I never use orms and don’t find them appealing, but one thing I do with my sqls may interest you.

I always wrap .query(…) or simply pass its result to a set of quantifiers: .all(), .one(), .opt(), run(), count(). These assert there’s 0+, 1, 0-1, 0, 0 rows.

This is useful to control singletons (and nonetons), otherwise you end up check-throwing every other sql line. One/opt de-array results automatically from T[] to T and T | undefined. Count returns a number.

Sometimes I add many() which means 1+, but that’s rare in practice, cause sqls that return 1+ are semantically non-singleton related but business logic related, so explicit check is better.

I also thought about .{run,count}([max,[min]]) sometimes to limit destructiveness of unbounded updates and deletes, but never implemented that in real projects.

Maybe there’s a better way but I’m fine with this one.

Edit: messed up paragraphs on my phone, now it’s ok

By @richwater - 4 months
This is a really cool project, but I'm not sure I like some of the APIs.

`orderByDesc` seems like it could be better suited for an object constant indicating the sort direction.

``` orderBy(OrderBy.Desc, user => user.age) ```

Overall still very nice and looking forward to seeing more development!

By @pjerem - 4 months
Oh, that’s Entity Framework but in typescript ?
By @codr7 - 3 months
Why are we trying so hard to pretend the database is something else?

I've had more success modelling database concepts directly in the language; tables, columns, keys, indexes, queries, records etc.

https://github.com/codr7/hostr/tree/main/src/Hostr/DB

By @tehlike - 4 months
This is like the lambda / Linq on .NET. Well done. Take a look at PRQL too. You may enjoy it, it may even help you simplify query transformations to sql.
By @gedy - 4 months
Thanks for this. While I have no problem with SQL, I enjoy the type checking, autocomplete, and 'compilation' this TS syntax gives you. Please continue!
By @mannyv - 4 months
It looks like this isn't really an ORM, it's more like a node-based layer to simplify DB access.

Which I actually like more, because I want to understand the database, not abstract it away. But dealing with SQL is/can be awkward. This library means I don't have to dynamically build sql queries in code.

Handy!

By @gsck - 4 months
A while back I wanted to do a project in NodeJS to refresh my JS skills a bit, wanted to find a nice ORM similar to EF because I use it so frequently but unfortunately didn't come across anything.

Ended up using drizzle and just hated every moment of it. This is definitely going in the "Use this eventually" folder!

By @ericyd - 4 months
I might have missed it but I would like to see what the return types look like, and how type safe they are. The query interface is interesting, I'm not sure I'm sold but if I don't know how to use the result then I'm not going to adopt it.
By @brap - 4 months
Pretty cool! The only thing I didn't like in the examples were things like .eq and .add, which are kind of a DSL, so it takes away from the "just plain JS" approach. But I assume it's because JS doesn't allow operator overloading?
By @bearjaws - 4 months
I am not sure I am understanding array-like in this context?

It seems to be more like knex or https://kysely.dev/

By @spankalee - 4 months
This looks really nice. It's not so much an ORM as a embedded DSL for SQL. The raw SQL with the tagged template literal is quite nice too.
By @atishay811 - 4 months
Just like we did HTML in JS via JSX or lit html, I wonder if we should have better SQL in JS that way.
By @todotask - 4 months
Qustar sounds nice, I would think "Exact" is what it is.
By @jdthedisciple - 4 months
So basically like entity framework & LINQ in the C# world

but for nodejs

By @arrty88 - 4 months
Very cool. Reminds me of linq to sql
By @EGreg - 4 months
"Codegen free"

why is codegen bad?

By @marcelr - 4 months
can i suggest saying “iterator api” instead of array-like?
By @nsonha - 4 months
> array-like API

why is this arbitrary property desirable?

By @layer8 - 4 months
The API doesn’t really look “array-like”.
By @fourseventy - 4 months
I've come to the conclusion that ORMs are good for simple queries like User.find_by(email: "john@snow.com"), but once you get beyond that you are better off just writing sql.
By @khy - 4 months
Scala has a library called Slick which takes a similar approach: https://scala-slick.org

The DSL is nice for simple querying and for composing queries based upon user input. But, for anything slightly complex, I found it's better to just use regular SQL.