Write raw SQL in TypeScript with full type safety — no type casts required!
This repository is a proof of concept (POC) for a typed query function in TypeScript. It allows you to write raw SQL queries and automatically infer the correct return types for SELECT statements. This provides a lightweight alternative to an ORM, keeping your database as the single source of truth for your schema.
- Define your database schema and seed it with data.
- Write raw SQL queries in
.sqlfiles. - Run
pnpm db:gento generate TypeScript types for your queries. - Use the
queryfunction in your application code to execute the queries and access typed results.
- SQLite (via
@libsql/client)
Support for additional databases may be added in the future.
Create a SQLite database with the required tables and data. For example, in this POC, the database includes tables for users, roles, and countries.
Adjust the database configuration in the file typed_sql/script.ts.
For each query you want to use in your application, create a .sql file in a queries folder. For example:
-- queries/users.sql
SELECT
u.id,
u.username,
u.country_code AS location,
r.rolename AS role
FROM
users u
INNER JOIN roles r ON u.role_id = r.id
ORDER BY
u.created_atRun the following command to generate TypeScript types for your queries:
pnpm db:genThis script:
- Connects to your database to retrieve the table schemas.
- Parses the SQL queries to infer the correct return types.
Import the query function and pass the filename of the SQL file to execute the query:
import { query } from './typed_sql/db'
const { rows, err } = await query('users')The rows object will have the following inferred type:
const rows:
| {
id: number
username: string
location: string | null
role: string
}[]
| nullThe rows array contains the results of the query, with each row typed according to the SQL query's output.
If an error occurs during the query, rows will be null, and the err object of type LibsqlError will contain the error details. This is already logged in the query function, so usually you only need to process it further if you want to know the error code, for example, or forward the error message elsewhere.
-
Support queries with parameters.
-
Support
INSERTandUPDATEqueries that end withRETURNING .... -
Support subqueries.
-
Make the approach agnostic to
@libsql/client.