# prest/prest: turning an existing Postgres database into a REST and MCP API

> pRESTd (PostgreSQL REST) exposes CRUD endpoints, custom SQL routes and a read-only MCP endpoint over a database you already have, without writing a backend. The interesting part is not the CRUD, it is how the query templates bind parameters.

**prest/prest** — PostgreSQL ➕ REST, low-code, simplify and accelerate development, ⚡ instant, realtime, high-performance on any Postgres application, existing or new, MCP server

- Repository: https://github.com/prest/prest
- Website: https://www.prestd.com
- Stars: 4,617 · Forks: 325
- Language: Go
- License: MIT
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/prest-prest

## The problem pRESTd removes: a schema that already exists but has no HTTP surface

Most teams reach for pRESTd at the point where a database is already designed and populated, and the work left over is the boring part: a handler per table, serialisation, pagination, filtering, an auth layer that reads the same rows the application reads. The project describes itself as a production-ready API that delivers instant REST and Model Context Protocol APIs on top of an existing or new Postgres database, covering CRUD, custom SQL routes, auth, ACL and a read-only MCP endpoint.

The audience is narrow and specific. It is backend engineers who are comfortable with SQL and uncomfortable with the amount of Go or Node required to expose that SQL over HTTP. It is also teams wiring an AI client to a database, since the MCP endpoint is read-only by design and the repository carries a dedicated docs page for MCP over HTTP and a page listing AI clients such as Cursor and Claude.

What it is not is a backend framework. There is no model layer, no migration DSL of its own beyond a vendored migration library, no business logic layer. The schema is the API contract. If your tables are named badly, the endpoints are named badly, and the project offers no rename step between the two.

## How the mechanism works: database, schema and table in the path, SQL templates underneath

The routing rule is the whole architecture in one line. According to the README, once pREST is pointed at Postgres through PREST_PG_URL or the pg.* keys or DATABASE_URL, you call:

```http
GET /{database}/{schema}/{table}
```

That path shape tells you the request flow. The service is a Go binary that opens a connection pool to Postgres, maps the three path segments onto a database, a schema and a table, and translates the HTTP verb into the corresponding SQL statement. The repository layout reflects this: adapters, controllers, middlewares, router, template, transactions and cache are separate top-level directories, and go.mod shows gorilla/mux for routing, jmoiron/sqlx and lib/pq for database access, and urfave/negroni/v3 for the middleware chain.

The part worth reading before anything else is the query template system. Custom SQL routes are Go templates over SQL text, and the project distinguishes sharply between two ways of getting a value into that text. Interpolation is screened: per the README, 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. Binding bypasses the screen entirely because the value travels to Postgres out of band. The README gives the contrast directly: an interpolated `SELECT * FROM articles WHERE slug = '{{.slug}}'` against a bound `SELECT * FROM articles WHERE slug = {{sqlVal "slug"}}`. Three helpers exist: {{sqlVal "key"}} for a single value rendering $1, {{sqlList "key"}} for a repeated query parameter rendering ($1,$2), and {{ident "key"}} for a table or column name, which cannot be bound and renders as a quoted identifier such as "public"."users". The binding helpers also reach headers as {{sqlVal "header.X-Application"}}, while credential headers including Authorization and Cookie are always withheld.

That design choice has a consequence the README states plainly: a search phrase containing a common word such as do, as or or is exactly what the interpolation screen refuses. So the screening is not a nicety layered on top of the template engine, it is the reason to use the binding helpers for every user-supplied value.

## Installing pRESTd with Docker Compose and making the first request

The README does not inline the install commands. It points to a Get pREST page in the documentation for Docker, Homebrew and Go, and the repository ships the pieces those instructions operate on: a Dockerfile, a Dockerfile.noplugins, a docker-compose.yml, a docker-compose-prod.yml, an install-manifests directory and a samples/prest.sample.toml.

The fastest path visible in the repository is Compose. Copy the environment example first, because the compose file reads it through env_file and the sample warns not to commit it.

```bash
cp .env.example .env
```

The sample file contains a single required setting, PREST_PG_URL, shown in two forms: a remote URL with ?sslmode=require for AWS RDS or Heroku, and a local one pointing at the compose Postgres service with ?sslmode=disable. The compose file defines an optional Postgres 18 service behind the local profile, so a self-contained run looks like this.

```bash
docker compose --profile local up
```

The prest service builds from the repository Dockerfile, restarts on failure, and publishes port 3000. It sets PREST_DEBUG=true, PREST_PG_CACHE=false, PREST_JWT_DEFAULT=false and PREST_CACHE_ENABLED=false, which means the default local stack has authentication and caching switched off. Do not carry those values into anything reachable from a network.

Once it is up, the first real call is the path from the README, against a table that exists in your database. The samples directory includes a Postman collection named prest_first_look.postman_collection.json for exactly this step, which is a more reliable starting point than guessing at your own schema.

For a source build, the README documents the local image build and the build arguments that stamp version metadata into the binary.

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

There is a real cost hidden in that Dockerfile. The default build target compiles the plugin system with CGO_ENABLED=1 and PREST_BUILD_PLUGINS=1, and it also builds the studio frontend with pnpm before the Go stage. The Dockerfile.noplugins variant exists because that plugin build is not always wanted. If you only need the API, the release target used by GoReleaser takes a prebuilt binary, which is the cheaper image.

## Where pRESTd is the wrong tool: schema exposure, the interpolation screen and the missing rollback story

The first limitation is structural rather than a bug. Every endpoint is derived from the schema, so the moment pRESTd is running, your table and column names are part of a public interface. Renaming a column is now an API breaking change. Teams that treat their database as an internal implementation detail will find this backwards, and no configuration in the repository changes it.

The second is the interpolation screen. It is a sensible default, but it changes what your routes can accept. A custom query written with interpolated values will reject a search phrase containing a SQL keyword once that phrase has more than one word. The README's own guidance is to bind instead, and to prefer binding for anything user-supplied, search phrases especially. If you have already written a set of interpolated templates, the migration is not a rename: each value has to move to {{sqlVal}} or {{sqlList}}, and {{ident}} cannot help you because identifiers cannot be bound.

The third is operational. The compose file sets restart: on-failure and the service is a single binary in front of your database, which means the connection pool sizing, the cache settings and the auth defaults are all things you configure rather than inherit. The README documents the OpenTelemetry variables as opt-in and comments them out, so telemetry is off until you bring up a collector and set PREST_OTEL_ENABLED along with the endpoint, insecure flag and service name. The README does not document a rollback procedure for a bad configuration change, and the release history shows why that matters: v2.4.0, v2.4.1 and v2.4.2 landed within about two weeks of each other in July and August 2026. Pin an image tag rather than tracking latest.

Finally, authentication is off by default in the shipped compose configuration. That is convenient for a first look and wrong for anything else.

## pRESTd against PostgREST: what actually differs

PostgREST is the obvious comparison, and the difference is not performance, it is where the logic lives. PostgREST maps HTTP requests onto PostgreSQL's own schema objects: tables, views and stored functions, with the database doing the filtering and the row-level security doing the authorisation. You get a smaller surface and you lean hard on Postgres features.

pRESTd keeps the database as the data store but puts a Go service in the middle. That service owns routing, the middleware chain, the caching layer, the JWT handling and the template engine that renders SQL from Go templates. The consequence is that pRESTd can do things a pure schema mapping cannot: {{sqlVal "header.X-Application"}} pulls a request header into the SQL, {{sqlList}} expands a repeated query parameter into an IN list, and a custom route can be a template rather than a function you deploy into the database. The cost is a second moving part and a second place where configuration can be wrong.

The other comparison the repository invites is with writing the backend yourself. That is the honest alternative for a schema you intend to keep private, or for a service where the HTTP contract must be stable while the tables churn. pRESTd wins when the schema is stable and the endpoints are the only missing piece.

## Licence, upgrade cost and maintenance signals

The project is MIT licensed, which is permissive and imposes no copyleft obligation on the rest of your stack. The repository also carries a Contributor License Agreement, and the README links a CLA assistant badge with a request to sign it before contributing. That affects people sending patches, not people running the binary. Nothing here is legal advice; if the CLA or the MIT terms matter to your organisation, read the LICENSE file and the CLA text rather than a summary.

On maintenance, the facts are concrete. The repository is not archived, and the last push was on 2026-09-10. Three releases shipped in the weeks before that: v2.4.0 on 2026-07-27, v2.4.1 on 2026-07-29 and v2.4.2 on 2026-08-11. go.mod declares go 1.26.0 and the Dockerfile builds on golang:1.26, so the toolchain requirement is recent and will move as Go releases.

The upgrade cost is dominated by two things. First, the Go toolchain and the CGO plugin build, which is why the no-plugins image exists. Second, the template semantics: if a future release changes how a helper renders, every custom query in your configuration is affected at once. The README documents the current helpers and the screening rules but does not document a deprecation policy for template syntax. Keep custom queries in version control next to the configuration that loads them, and test them against a staging database before upgrading.

## Conclusion

Adopt pRESTd when you already have a Postgres schema and want HTTP endpoints over it this week, and when you accept that the database is your API contract. Do not adopt it when you need a full application framework, when you want to hide the schema behind hand-written resources, or when you cannot run a service with database credentials in its environment. Before committing, verify three things yourself: that your Postgres is at least 9.5, that your custom query templates use {{sqlVal}} or {{sqlList}} rather than interpolated {{.key}} for anything a user supplies, and that the access control configuration matches the tables you intend to expose. The interpolation screen refuses multi-word values containing SQL keywords, so a search route built on {{.q}} will fail on ordinary phrases; that single behaviour decides whether the migration is a day or a week.

## FAQ

### What does prest mean in the name of the prest/prest project?

The README expands it: pREST stands for PostgreSQL REST, and the project is also written as pRESTd. The name describes what it does rather than carrying a separate meaning.

### Which PostgreSQL versions does pRESTd support?

The README states PostgreSQL version 9.5 or higher. The compose file in the repository uses a Postgres 18 image for the optional local service.

### How do I install pRESTd on my machine?

The README lists Docker, Homebrew and Go as install options and links a Get pREST documentation page for the commands. The repository also ships a docker-compose.yml whose prest service builds from the local Dockerfile and publishes port 3000.

### Does pRESTd require me to write a backend?

No. The README describes it as delivering instant REST and MCP APIs over an existing or new Postgres database, covering CRUD, custom SQL routes, auth and ACL without hand-writing a backend. Custom SQL routes are Go templates over SQL text rather than application code.

### Why does a pRESTd custom query reject my search phrase with a 400?

Because the value was interpolated rather than bound. The README states that interpolated values carrying quotes, --, ::, or, for multi-word values, a SQL keyword are refused, and a refused interpolated value fails the request with 400. The fix is to use {{sqlVal "key"}} or {{sqlList "key"}}, which send the value to Postgres out of band.

## Sources

- [License: MIT](https://github.com/prest/prest/blob/main/LICENSE)
- [prest/prest on GitHub](https://github.com/prest/prest)
- [Project website](https://www.prestd.com)
- [README](https://github.com/prest/prest/blob/main/README.md)
- [Releases](https://github.com/prest/prest/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/prest-prest
