← Back to Blog

pREST v2.0.0-rc6 — Hexagonal Architecture and the Adapter Refactor

pREST v2.0.0-rc6 is a release-candidate milestone for pREST, the open-source tool that turns PostgreSQL into a REST API. The headline change is architectural: the monolithic Adapter interface is decomposed into smaller port interfaces, and HTTP controllers are rewritten as dependency-injected handlers following hexagonal (ports-and-adapters) design. The composite Adapter still exists so the Postgres implementation satisfies one type and existing wiring stays compatible. See the v2.0.0-rc6 release for binaries and Docker images.

Table of Contents

What changed in v2.0.0-rc6

pREST exposes PostgreSQL tables and custom SQL scripts over HTTP — a low-code REST layer for existing or new Postgres applications. Version 2 is a major internal overhaul shipped as release candidates; v2.0.0-rc6 is the latest RC, not final GA.

Highlights from the changelog:

  • Architecture — Adapter split, DI controllers, hexagonal refactor (PR #968)
  • Security — JWT auth bypass fix when enforcement runs without a key (#960)
  • Query — OR clause support (#958)
  • Tooling — GoReleaser Docker images (#953), Go 1.26, split unit/integration CI
  • Observabilityslog migration (#950)

The architecture work is the through-line. Everything else — security fixes, query features, build tooling — lands on a cleaner foundation.

The problem: monolithic Adapter and global controllers

Before v2, pREST’s database layer was expressed as a single large Adapter interface. That one type owned catalog queries (databases, schemas, tables), request-to-SQL building, query execution, permissions checks, script running, and transaction management.

Controllers compounded the coupling. Package-level functions reached into global config and the global adapter instance. A handler that only needed to execute a SELECT still lived in a package implicitly tied to the full adapter surface.

The practical consequences:

  • Testing — mocking required satisfying the entire Adapter interface even when a test only exercised one code path.
  • Change isolation — edits to catalog SQL building risked touching CRUD or script paths because everything shared one interface boundary.
  • Extensibility — adding a new database backend meant implementing every method on day one, with no way to depend on a subset.

Hexagonal architecture in pREST

v2 applies hexagonal architecture (ports and adapters). The application core — HTTP handlers — depends on narrow port interfaces. Concrete implementations (the Postgres adapter) sit on the outside as driven adapters.

flowchart TB
    subgraph driving [Driving adapters]
        HTTP[HTTP controllers]
    end
    subgraph core [Application core]
        Handlers[AuthHandler CatalogHandler CRUDHandler ...]
        Ports[Port interfaces]
    end
    subgraph driven [Driven adapters]
        PG[postgres.Adapter]
    end
    HTTP --> Handlers
    Handlers --> Ports
    Ports --> PG

Port interfaces

The monolithic Adapter is split into focused interfaces in adapters/:

Port Responsibility
CatalogQuerier SQL for listing databases, schemas, and tables
RequestQueryBuilder Build queries from HTTP request parameters
QueryExecutor Execute queries and return result scanners
SQLBuilder Construct INSERT/UPDATE/DELETE/SELECT statements
PermissionsChecker Table- and field-level access control
ScriptRunner Execute custom SQL scripts
DatabaseRegistry Resolve database names and connections
TransactionManager Begin and manage transactions
LegacyExecutor Non-context execution with transaction support

Composite Adapter

The composite Adapter interface still exists. It embeds all port interfaces so the Postgres implementation satisfies one type and callers that need the full surface can keep using Adapter:

// adapters/adapter.go
type Adapter interface {
    RequestQueryBuilder
    QueryExecutor
    CatalogQuerier
    SQLBuilder
    PermissionsChecker
    ScriptRunner
    DatabaseRegistry
    TransactionManager
    LegacyExecutor
}

Handlers depend only on the ports they need. CatalogHandler takes a CatalogQuerier; CRUDHandler takes QueryExecutor, PermissionsChecker, and related ports — not the full adapter.

Driving adapters

HTTP controllers in controllers/ are structs with injected dependencies: AuthHandler, CatalogHandler, CRUDHandler, TableHandler, ScriptHandler, and HealthHandler. The router registers methods on these structs instead of package-level functions.

Dependency injection wiring

Dependency assembly lives in controllers/deps.go. A Deps struct holds typed port references:

type Deps struct {
    Catalog  adapters.CatalogQuerier
    Builder  adapters.RequestQueryBuilder
    Executor adapters.QueryExecutor
    SQL      adapters.SQLBuilder
    Perms    adapters.PermissionsChecker
    Scripts  adapters.ScriptRunner
    DB       adapters.DatabaseRegistry
    Cache    ResponseCacher
    // ...
}

NewDepsFromConfig maps a single Postgres Adapter instance into each port field. The same concrete object is passed multiple times, narrowed to different interface types. NewHandlers then constructs each handler with only its required deps.

Middleware follows the same pattern. AccessControl and CRUDStack receive a PermissionsChecker via injection instead of reaching for global state.

Request flow example: CRUD read

A GET /{database}/{schema}/{table} request flows like this:

  1. Router — dispatches to the CRUD middleware stack
  2. CRUDStack middleware — calls PermissionsChecker.TablePermissions to verify the user can read the table
  3. CRUDHandler.Select — calls PermissionsChecker.FieldsPermissions for column-level access, builds SQL via the query builder, executes through QueryExecutor.QueryCtx
  4. Response — JSON bytes returned to the client; optionally cached via ResponseCacher

Each step depends on an interface, not a global. Unit tests can inject gomock implementations per port without a live database.

What this unlocks

  • Testability — gomock and sqlmock unit tests per handler; integration tests run in a separate CI workflow against Docker Compose Postgres
  • CI split — dedicated test-unit.yml and test-integration.yml workflows replace a single combined test job
  • Extensibility — a new database backend implements the port interfaces its handlers need; HTTP layer stays unchanged
  • Safer defaults — bcrypt replaces MD5 as the default password encryption algorithm in v2 config

Other notable release changes

Beyond architecture, v2.0.0-rc6 includes meaningful functional and operational improvements.

JWT auth bypass fix (#960) — when JWT enforcement was enabled but no signing key was configured, requests could bypass authentication. The fix ensures misconfiguration fails closed.

OR clause support (#958) — filter queries can now express OR conditions, expanding the query surface for clients that build filters from HTTP parameters.

GoReleaser Docker images (#953) — official container images are now built through GoReleaser, aligning release artifacts with binary builds.

Structured logging (#950) — the API layer migrates to Go’s slog for structured, leveled logging.

Trying v2.0.0-rc6

This is a release candidate — pin the version and watch the prest/prest repo for GA.

Docker:

docker pull prest/prest:v2.0.0-rc6

Binaries — download platform-specific builds from the v2.0.0-rc6 release assets.

For issues, feedback, or migration questions, open a discussion or issue on GitHub. The v2.0.0-rc6 changelog links to every merged PR.

FAQ

What is pREST v2.0.0-rc6?

pREST v2.0.0-rc6 is the latest release candidate for pREST 2.0, an open-source Go tool that exposes PostgreSQL as a REST API. It includes a major architectural refactor (hexagonal design, Adapter decomposition, dependency-injected controllers) plus security fixes, OR clause support, GoReleaser Docker images, and structured logging via slog.

What is the Adapter interface refactor?

The v2 refactor splits pREST’s monolithic Adapter interface into nine smaller port interfaces (CatalogQuerier, QueryExecutor, RequestQueryBuilder, and others). A composite Adapter interface still embeds all ports so the Postgres implementation satisfies one type. HTTP handlers depend only on the ports they need, which improves testability and makes future database backends easier to add.

What is hexagonal architecture in the context of pREST?

In pREST v2, hexagonal architecture means HTTP controllers (driving adapters) depend on port interfaces in the application core, and the Postgres adapter (driven adapter) implements those ports. Dependencies point inward: handlers never import Postgres-specific code. Wiring happens at startup through NewDepsFromConfig and NewHandlers.

Is v2.0.0-rc6 production-ready?

v2.0.0-rc6 is a release candidate, not a final GA release. It is suitable for evaluation and staging environments where teams can pin the version and provide feedback. Production deployments should wait for a stable v2.0.0 GA or accept RC-level risk with a pinned image tag and a rollback plan.