TypeScript library that translates SQL queries to the equivalent PostgREST-compitable HTTP requests and client library code. Works on both browser and server.
PostgREST is a REST API that auto-generates endpoints based on relations in your database (public
schema only by default). It uses JWTs and RLS policies to handle authorization.
The PostgREST API supports a lot of SQL-like features including:
- Vertical filtering (select only the columns you care about)
- Horizontal filtering (filter rows by comparing data in columns:
=
,>
,<
,in
,like
, etc) - Sorting
- Limit and offset
- Resource embeddings (joins to other relations using foreign keys)
- Aggregate operations (
count()
,sum()
,avg()
,min()
,max()
) - Nested
AND
/OR
expressions - Aliasing and casting
- JSON columns (selecting, filtering, and sorting)
This library takes the SQL input and translates it to 1-to-1 to the equivalent PostgREST syntax. Any unsupported SQL will throw an error.
The following SQL:
select
title,
description
from
books
where
description ilike '%cheese%'
order by
title desc
limit
5
offset
10
Will get translated to:
cURL
curl -G http://localhost:54321/rest/v1/books \
-d "select=title,description" \
-d "description=ilike.*cheese*" \
-d "order=title.desc" \
-d "limit=5" \
-d "offset=10"
Raw HTTP
GET /rest/v1/books?select=title,description&description=ilike.*cheese*&order=title.desc&limit=5&offset=10 HTTP/1.1
Host: localhost:54321
supabase-js
const { data, error } = await supabase
.from('books')
.select(
`
title,
description
`
)
.ilike('description', '%cheese%')
.order('title', { ascending: false })
.range(10, 15)
npm i @supabase/sql-to-rest
yarn add @supabase/sql-to-rest
Note: This library is pre-1.0, so expect slight API changes over time.
import { processSql, renderHttp, formatCurl } from '@supabase/sql-to-rest'
// Process SQL into intermediate PostgREST AST
const statement = await processSql(`
select
*
from
books
`)
// Render the AST into an HTTP request
const httpRequest = await renderHttp(statement)
// Format the HTTP request as a cURL command (requires base URL)
const curlCommand = formatCurl('http://localhost:54321/rest/v1', httpRequest)
console.log(curlCommand)
// curl http://localhost:54321/rest/v1/books
// Or use it directly
const response = await fetch(`http://localhost:54321/rest/v1${httpRequest.fullPath}`, {
method: httpRequest.method,
})
Takes a SQL string and converts it into a PostgREST abstract syntax tree (AST) called a Statement
. This is an intermediate object that can later be rendered to your language/protocol of choice.
import { processSql } from '@supabase/sql-to-rest'
const statement = await processSql(`
select
*
from
books
`)
Outputs a Promise<Statement>
:
{
type: 'select',
from: 'books',
targets: [
{
type: 'column-target',
column: '*',
alias: undefined,
},
],
filter: undefined,
sorts: [],
limit: undefined
}
Takes the intermediate Statement
and renders it as an HTTP request.
import { processSql, renderHttp } from '@supabase/sql-to-rest'
const statement = await processSql(`
select
*
from
books
`)
const httpRequest = await renderHttp(statement)
Outputs a Promise<HttpRequest>
:
{
method: 'GET',
path: '/books',
params: URLSearchParams {},
fullPath: [Getter] // combines path with the query params
}
An HttpRequest
can also be formatted as a cURL
command or as raw HTTP.
import {
// ...
formatCurl,
} from '@supabase/sql-to-rest'
// ...
const curlCommand = formatCurl('http://localhost:54321/rest/v1', httpRequest)
Outputs:
curl http://localhost:54321/rest/v1/books
import {
// ...
formatHttp,
} from '@supabase/sql-to-rest'
// ...
const rawHttp = formatHttp('http://localhost:54321/rest/v1', httpRequest)
Outputs:
GET /rest/v1/books HTTP/1.1
Host: localhost:54321
Takes the intermediate Statement
and renders it as supabase-js
client code.
import { processSql, renderSupabaseJs } from '@supabase/sql-to-rest'
const statement = await processSql(`
select
*
from
books
`)
const { code } = await renderSupabaseJs(statement)
Outputs a Promise<SupabaseJsQuery>
, where code
contains:
const { data, error } = await supabase.from('books').select()
The rendered JS code is automatically formatted using prettier
.
- The SQL string is parsed into a PostgreSQL abstract syntax tree (AST) using
libpg-query-node
, a JavaScript SQL parser that uses C code from the official PostgreSQL codebase (compiled to WASM for browser targets, NAPI for Node targets). Supports Postgres 15 syntax. - The PostgreSQL AST is translated into a much smaller and simpler PostgREST AST. Since PostgREST supports a subset of SQL syntax, any unsupported SQL operation will throw an
UnsupportedError
with a description of exactly what wasn't supported. - The intermediate PostgREST AST can be rendered to your language/protocol of choice. Currently supports HTTP (with
cURL
and raw HTTP formatters), andsupabase-js
code (which wraps PostgREST). Other languages are on the roadmap (PR's welcome!)
-
select
statements (GET
requests) -
insert
statements (POST
requests)-
on conflict update
(upsert)
-
-
update
statements (PATCH
requests) -
delete
statements (DELETE
requests) -
explain
statements (Execution plan)
-
=
(eq
) -
>
(gt
) -
>=
(gte
) -
<
(lt
) -
<=
(lte
) -
<>
or!=
(neq
) -
like
(like
) -
ilike
(ilike
) -
~
(match
) -
~*
(imatch
) -
in
(in
) -
is
(is
): partial support, onlyis null
for now -
is distinct from
(isdistinct
) -
@@
(fts
,plfts
,phfts
,wfts
) -
@>
(cs
) -
<@
(cd
) -
&&
(ov
) -
<<
(sl
) -
>>
(sr
) -
&<
(nxr
) -
&>
(nxl
) -
-|-
(adj
)
-
not
(not
) -
or
(or
) -
and
(and
) -
all
(all
) -
any
(any
)
-
asc
(asc
) -
desc
(desc
) -
nulls first
(nullsfirst
) -
nulls last
(nullslast
)
-
limit
(limit
) -
offset
(offset
) - HTTP range headers
-
count()
-
sum()
-
avg()
-
max()
-
min()
- aggregate over entire table
- aggregate on joined table column
- aggregate with
group by
- aggregate with
group by
on joined table column
Joins (Resource Embedding)
SQL joins are supported using PostgREST resource embeddings with the spread ...
syntax (flattens joined table into primary table).
- column aliases
- table aliases
- column casts (in select target only)
- aggregate function casts (in select target only)
JSON columns (eg. select metadata->'person'->>'name'
) are supported in the following places:
- select targets
- filters
- sorts
- HTTP
- cURL formatter
- Raw HTTP formatter
-
supabase-js
-
supabase-flutter
-
supabase-swift
-
supabase-py
-
supabase-csharp
-
supabase-kt
Thankfully no. We use libpg-query-node
which takes source code from the real PostgreSQL parser and wraps it in JavaScript bindings. It compiles the C code into WASM for browser environments and uses native NAPI bindings for server environments.
This means we never have to worry about the SQL itself getting parsed incorrectly - it uses the exact same code as the actual PostgreSQL database. This library uses code from PostgreSQL 15.
It can't. PostgREST only supports a subset of SQL-like features (by design), so this library only translates features that can be mapped 1-to-1.
When it detects SQL that doesn't translate (eg. sub-queries), it will throw an UnsupportedError
with a description of exactly what couldn't be translated.
We've built unit tests for every feature supported. The vast majority of PostgREST features have been implemented, but it doesn't cover 100% yet (see Roadmap). If you discover an error in the translation, please submit an issue.
MIT