GitHub - prest/prest: Instant REST and MCP APIs for PostgreSQL and MySQL, on existing or new databases. CRUD, custom SQL routes, auth and ACL without writing a backend. Low-code, ⚡ realtime, high-performance. Native MySQL support since v2.5. · GitHub
Skip to content
prestPublic

About

Instant REST and MCP APIs for PostgreSQL and MySQL, on existing or new databases. CRUD, custom SQL routes, auth and ACL without writing a backend. Low-code, ⚡ realtime, high-performance. Native MySQL support since v2.5.

Topics

Resources

Code of conduct

Contributing

Stars

4.6k stars

Watchers

73 watching

Forks

Latest commit

 

History

2,062 Commits

Folders and files

Repository files navigation

pRESTd

Unit tests Go Reference Go Report Card codecov Homebrew Discord

pREST (PostgreSQL REST) is a production-ready API that delivers instant REST and Model Context Protocol (MCP) APIs on your existing or new SQL database — CRUD, custom SQL routes, auth, ACL, and a read-only MCP endpoint — without hand-writing a backend.

PostgreSQL 9.5+ is the default engine. MySQL 8.0.19+ is a separate native dialect from v2.5.0. Read-only MCP on MySQL is v2.5.1. Set engine = "mysql" to select it. An unset engine stays PostgreSQL. MariaDB, TiDB, and Aurora MySQL stay on the roadmap.

PostgreSQL 9.5+ (default). MySQL 8.0.19+ with engine = "mysql".

Contributor License Agreement — CLA assistant

pREST - instant, realtime, high-performance on PostgreSQL | Product Hunt

Documentation

Full documentation lives at docs.prestd.com.

Topic Link
Get pREST (Docker, Homebrew, Go) Get pREST
Configuration Configuring pREST
API reference API Reference
MCP over HTTP MCP over HTTP
Multi-database Multi-database
Databases & roadmap Databases · Roadmap
MySQL 8 MySQL · v2.5.1
AI clients (Cursor, Claude, …) AI and MCP

Quick start

Install and run options (Docker, Homebrew, or Go) are documented in Get pREST. Point pREST at Postgres (PREST_PG_URL or pg.* / DATABASE_URL), then call:

GET /{database}/{schema}/{table}

MySQL 8.0.19+ uses the same path. {schema} is the MySQL database. Set the engine and a mysql:// URL. The port defaults to 3306 when unset. PREST_DB_* is read first, then the legacy PREST_PG_* names:

engine = "mysql"

[pg]
url = "mysql://user:pass@localhost:3306/mydb"
export PREST_ENGINE=mysql
export PREST_DB_URL='mysql://user:pass@localhost:3306/mydb'

A missing user or database fails startup. Postgres defaults (user postgres, database prest, port 5432) are not copied onto a MySQL engine. Guide: MySQL.

See Configuring pREST for auth, ACL, custom queries, and MCP.

Serving several databases

pREST can serve multiple databases in two modes:

Registry mode

Define a [[databases]] registry in prest.toml or use environment variables (DATABASE_ALIAS_<n> / DATABASE_URL_<n>). Aliases may differ from database names, and entries can be on the same host or different hosts:

[[databases]]
alias = "main"
url = "postgres://user:pass@localhost:5432/prest_main?sslmode=disable"

[[databases]]
alias = "tenant-1"
url = "postgres://user:pass@localhost:5432/tenant_1?sslmode=disable"

[[databases]]
alias = "reporting"
url = "mysql://user:pass@127.0.0.1:3306/reports?tls=false"

Or via environment:

DATABASE_ALIAS_1=main
DATABASE_URL_1="postgres://user:pass@localhost:5432/prest_main?sslmode=disable"
DATABASE_ALIAS_2=tenant-1
DATABASE_URL_2="postgres://user:pass@localhost:5432/tenant_1?sslmode=disable"

An entry that is unreachable at start-up is logged and answers 503 ("database unavailable") until it comes up; start-up fails only when every entry is unreachable or the configuration is invalid.

No-registry mode

Use pg.database plus pg.allowed_databases on the [pg] host with the [pg] credentials. Both Postgres and MySQL are supported. On MySQL, {schema} must equal {database}:

[pg]
database = "prest"
allowed_databases = ["secondary-db"]

Or via environment (comma-separated for allowed databases):

PREST_PG_DATABASE=prest
PREST_PG_ALLOWED_DATABASES=secondary-db,another-db
# or for MySQL:
PREST_DB_DATABASE=mydb
PREST_DB_ALLOWED_DATABASES=other-db

Catalog and MCP

The /databases endpoint and MCP prest.list_databases show only the served databases. In registry mode, that is the configured aliases. Without a registry, that is pg.database plus pg.allowed_databases.

Postgres supports up to 64 open pools. MySQL creates one pool per served database.

Migrating from pg.single

The pg.single configuration key has been removed and is ignored with a warning. Behavior changes:

  • Allow-listed names are always served. Previously, pg.single = true (the default) required requests to be for the main pg.database; allow-listed names were ignored. Now they are served whether the deprecated key is set or not.
  • A registry is no longer limited to pg.database. Previously, with both a registry and pg.single = true, every request was restricted to the main database. Now a registry serves its configured aliases freely.
  • /databases and MCP list only served databases. Requests for unlisted databases return 400 "database not registered".
  • On MySQL without a registry, /{database}/{other-schema}/… returns 404. On MySQL a schema is a database, so {schema} must equal {database}. Previously any database the MySQL user had grants on was reachable through pg.database.

Passing values to query scripts

Values a _QUERIES script template interpolates become part of the SQL text, so pREST screens them: anything carrying quotes, --, ::, or — for multi-word values — a SQL keyword is refused, and a refused value that is interpolated fails the request with 400.

Bind free-form values instead, and the screen does not apply at all. On PostgreSQL, a bound value travels to the database out of band, where it can never be parsed as SQL:

-- interpolated: screened, and rejected for values like 'compra do mes'
SELECT * FROM articles WHERE slug = '{{.slug}}'

-- bound: the caller's value arrives verbatim, whatever it contains
SELECT * FROM articles WHERE slug = {{sqlVal "slug"}}
Helper Use for Renders
{{sqlVal "key"}} a single value $1
{{sqlList "key"}} a repeated query parameter (?tag=a&tag=b) ($1,$2)
{{ident "key"}} a table/column name, which cannot be bound "public"."users"

That table is the PostgreSQL rendering. On MySQL, {{sqlVal "key"}} renders ?, {{sqlList "key"}} renders (?,?), and {{ident "key"}} renders backtick names such as `shop`.`users`. With the default prepare = false, MySQL interpolates those ? placeholders into the statement. prepare = true uses binary prepared statements, and then the values travel out of band.

sqlVal and sqlList also reach headers as {{sqlVal "header.X-Application"}}. Credential headers (Authorization, Cookie, …) are always withheld.

Prefer binding for anything user-supplied — search phrases especially, since a phrase containing a common word such as do, as or or is exactly what the interpolation screen refuses.

1-Click Deploy

Heroku

Deploy to Heroku and get a realtime RESTful API backed by Heroku Postgres:

Deploy to Heroku

More: Deploy in Heroku.

Contributing

Contributions are welcome. See the Development Guide and Contributing to pREST. Please sign the CLA.

Questions? GitHub Discussions or Discord.

Testing

Run unit tests locally:

make test-unit

Run integration suites inside Docker (no local database required):

# Postgres (full stack: default, auth, multicluster, queries) — also: make test-integration
make test-integration-postgres

# TimescaleDB (Timescale-specific E2E only)
make test-integration-timescaledb

# MySQL 8 (./integration/mysql/... only). Default image mysql:8.4.
# Override: MYSQL_IMAGE=mysql:8.0 make test-integration-mysql
make test-integration-mysql

Or with Docker Compose:

docker compose -f integration/postgres/docker-compose.yml up -d --wait \
  postgres postgres-b db-init prestd prestd-multicluster prestd-auth prestd-queries
docker compose -f integration/postgres/docker-compose.yml run --rm --no-deps tests
docker compose -f integration/postgres/docker-compose.yml down -v --remove-orphans

Postgres compose runs ./integration/suites/... and ./integration/postgres/.... TimescaleDB compose runs ./integration/timescaledb/... only (see .github/workflows/test-integration-timescaledb.yml). MySQL compose runs ./integration/mysql/... only (see .github/workflows/test-integration-mysql.yml). CI runs mysql:8.0, mysql:8.4, and mysql:latest. Network tests require PREST_TEST_URL (and flavor-specific URLs for the Postgres job); outside Compose those tests skip when the URLs are unset.

Example: Docker Build

Build the Docker image locally for development (compiles from source):

docker build -t prest/prest:latest .

For release builds, GoReleaser uses the same Dockerfile / Dockerfile.noplugins with a pre-built prestd binary. Local source builds can pass version metadata via build arguments:

docker build \
  --build-arg VERSION=v1.0.0 \
  --build-arg COMMIT=hash \
  --build-arg DATE=2026-02-11 \
  -t prest/prest:latest .

About

Instant REST and MCP APIs for PostgreSQL and MySQL, on existing or new databases. CRUD, custom SQL routes, auth and ACL without writing a backend. Low-code, ⚡ realtime, high-performance. Native MySQL support since v2.5.

Topics

Resources

Code of conduct

Contributing

Stars

4.6k stars

Watchers

73 watching

Forks

Releases

Sponsor this project

Packages

Used by

Contributors

Languages