DEV Community

Cover image for [sqlx-ts] compile-time checked queries without DSL in Typescript
Jason Shin
Jason Shin

Posted on • Updated on

[sqlx-ts] compile-time checked queries without DSL in Typescript

Summary

[Github | Demo]

This story is about sqlx-ts. It is a CLI tool that provides

  • Compile-time checked queries in your TypeScript code
  • TypeScript type generation against those SQLs
  • Database agnostic API
  • Work with both Typescript and Javascript

In short, it enables you to never ship broken and unoptimized queries to production. Here is an example of sqlx-ts running against PostgreSQL (but it works with MySQL as well)

in the video, sqlx-ts performed

  1. SQL check to check if the SQL is compatible with PG
  2. Generate Typescript interfaces so you can use the type to keep your code type-safe

As a result, you can never deploy SQL queries that are broken or unoptimized. Optimizing query is easier with raw SQLs as you can directly run SQL analyzer instead of grabbing a generated SQLs from ORMs.

sqlx-ts can work with complex queries as well such as joins

Design


sqlx-ts core is written in Rust. This enables a few key fundamentals

Installation

For a full guide, check the official documentation

First, install sqlx-ts

npm install sqlx-ts
# or if yarn
yarn add sqlx-ts
Enter fullscreen mode Exit fullscreen mode

installing sqlx-ts NPM module also installs Rust binary of sqlx-ts.

How to use

Check the full getting started guide

Check --help for all supported CLI arguments

npx sqlx-ts --help
Enter fullscreen mode Exit fullscreen mode

You need to have a database with schema running locally (for example)

$ npx sqlx-ts <path to project> --db-host=127.0.0.1 --db-port=4321 --db-type=postgres --db-pass=postgres --db-user=postgres --db-name=sqlx-ts
Enter fullscreen mode Exit fullscreen mode

Above command will search the target directory and validate all detected SQLs in-code against the target database's information_schema.

That's it! All your queries are valid if sqlx-ts did not throw an error.

Next, we can try generating TypeScript types against the detected SQLs

// src/app/index.ts
(async () => {
    const someQuery = await client.query(sql`
        SELECT items.id
        FROM items
    `)
    for (const row of someQuery.rows) {
        const { tableId, points } = row
        console.log(tableId, points)
    }
})();
Enter fullscreen mode Exit fullscreen mode

Run npx sqlx-ts <path to project> --db-host=127.0.0.1 --db-port=4321 --db-type=postgres --db-pass=postgres --db-user=postgres --db-name=sqlx-ts -g *note that I added -g in the end.

The command generated

// src/app/index.queries.ts
export type SomeQueryParams = [];


export interface ISomeQueryResult {
    food_type: string;
    id: number;
    points: number;
    table_id: number;
    time_takes_to_cook: number;
};


export interface ISomeQueryQuery {
    params: SomeQueryParams;
    result: ISomeQueryResult;
};
Enter fullscreen mode Exit fullscreen mode

(If you don't like the generated type's formatting, you can simply post-process them using prettier)

Now you can use the generated types against the database queries in-code

// src/app/index.ts
import { ISomeQueryResult } from './index.queries'

(async () => {
    const someQuery = await client.query<ISomeQueryResult>(sql`
        SELECT items.id
        FROM items
    `)
    for (const row of someQuery.rows) {
        const { tableId, points } = row
        console.log(tableId, points)
    }
})();
Enter fullscreen mode Exit fullscreen mode

As a result, any changes in the schema would break the query during build-time.

You can also use annotations to

  • override database target
  • provide custom name for queries
  • override generated types

Supported features

You can imagine 95% of SQL features are supported for both SQL check and type-generation features.

feature SQL check TS generation Future Roadmap
SELECT yes yes N/A
UPDATE yes yes N/A
DELETE yes yes N/A
INSERT yes yes N/A
JOINs yes yes N/A
enums yes not yet Will be supported in the near future
SQL analyzer no N/A Will be supported in the near future as this will be a unique feature that sqlx-ts provides
Process .sql files no N/A Will be added soon

Motivations

The project is largely inspired by a library in Rust sqlx. I'm always interested in how to keep code safe especially during compile time. Rust and its ecosystem always have something to learn and port over into TypeScript world for greater good.

Thank you and please report any issues on Github

Top comments (0)