REST Transport: Annotation-Driven, Not Auto-Wired
There are broadly two schools of thought on REST API generation from a database schema: auto-wire everything, or annotate what you want exposed. FraiseQL chooses annotation. Here’s why, and what that means in practice.
The auto-wiring approach
Section titled “The auto-wiring approach”PostgREST automatically exposes every PostgreSQL table and view as a REST endpoint. Point it at your database, and you have a fully functional REST API immediately — zero configuration, zero code.
The tradeoff: you expose everything your database user can see, and the REST surface is defined by your database schema. Adding a view for an internal caching optimization makes it a REST endpoint. Renaming a view changes your API. Your API design is coupled to your storage design.
PostgREST handles this with role-based permissions at the PostgreSQL level, which is a reasonable approach — but it means your API surface control is scattered across database permissions rather than visible in your schema definition.
FraiseQL’s choice: explicit annotations
Section titled “FraiseQL’s choice: explicit annotations”FraiseQL requires explicit annotations. You decide which operations get REST endpoints, what paths they use, and what HTTP methods they respond to:
@fraiseql.query( rest_path="/users/{id}", rest_method="GET",)def get_user(id: UUID) -> User: return fraiseql.config(sql_source="v_user")An operation without rest_path is GraphQL-only. Add rest_path and rest_method and it becomes available as a REST endpoint with the same auth, rate limiting, and RBAC as the GraphQL operation.
You can expose the same underlying view at multiple REST paths with different constraints, or keep internal views entirely out of the REST surface.
Why explicit annotation works better for our use cases
Section titled “Why explicit annotation works better for our use cases”Predictable API surface. You know exactly what’s exposed as REST because you annotated it. There are no surprise endpoints because someone added a convenience view to the database. The REST surface is defined in your schema files, not inferred from database permissions.
Reviewable in code. When a new REST endpoint is added, it appears as a diff in your schema files. Code review catches it. The REST surface is version-controlled and auditable alongside the rest of your schema.
Versionable paths. rest_path is a string you control. You can change the URL path — /v2/users/{id} — without touching the database view. The path is a schema concern, not a storage concern.
Safe internal views. A view you add for debugging, reporting, or internal tooling is not automatically a REST endpoint. You have to explicitly annotate it. The default is private.
Secure by default. If you add rest_path to an operation that is also available via gRPC, both transports enforce the same RBAC. There’s no risk of accidentally exposing a gRPC operation as a public REST endpoint — you’d have to annotate it.
OpenAPI generation
Section titled “OpenAPI generation”Because annotations are explicit, the compiler can generate an accurate OpenAPI 3.0.3 specification from them. Every annotated operation becomes an OpenAPI path item. Parameters come from the query signature. Response schemas come from the return type.
The spec is available at GET /rest/v1/openapi.json at runtime (the default path is /rest/v1; configure with [rest] path). You can also generate it at compile time with fraiseql-cli openapi. No manual YAML. No spec drift — the spec is generated from the same source as the endpoint.
This is only possible because the REST surface is defined precisely. Auto-wired APIs can generate specs, but the spec becomes a reflection of whatever the database exposes at that moment — not a stable API contract.
Migrating from PostgREST
Section titled “Migrating from PostgREST”If you’re moving from PostgREST, the migration path is:
- Your existing PostgreSQL views work as
sql_sourcevalues — no rewrite needed. - For each PostgREST endpoint you want to keep, add
rest_pathandrest_methodannotations to the corresponding query definition. - The explicit annotation step forces a review of what you actually want public. Views that existed for internal reasons don’t automatically become REST endpoints.
See Migrating from PostgREST for a full walkthrough.
When auto-wiring makes sense
Section titled “When auto-wiring makes sense”Auto-wiring is not wrong. It’s the right choice for a specific set of requirements.
If you want every table and view exposed with zero configuration — for internal tooling, admin panels, rapid prototyping, or a project where the database schema is the API design — PostgREST is the right tool. The zero-config model is a feature, not a limitation.
FraiseQL is for when you want control over your API surface. The explicit annotation step is overhead that pays off when you need:
- A public API with a stable contract independent of storage changes
- A multi-transport API (REST + GraphQL + gRPC) from the same schema
- Fine-grained control over what’s exposed without database-level permission management
- OpenAPI generation from a single source of truth
Neither approach is universally better. They reflect different philosophies about where API design belongs.