When is REST from the database enough?
Every early stage team faces the same fork. You can stand up a custom API service and write handlers for each screen. Or you can point PostgREST at PostgreSQL and let the schema define the HTTP surface. The second path feels like cheating because it is fast. The first path feels like adulthood because it is verbose. Neither is universally correct.
The useful question is not which choice makes engineers feel serious. It is whether your product's rules live in tables and permissions, or in orchestration that should stay out of the database wire protocol.
What "REST from the database" means
Tools like PostgREST read your Postgres schema and serve REST endpoints shaped by tables, views, and functions. Filters, ordering, and embeds follow foreign keys. Roles in the database become roles on the API. Row level security decides which rows survive the filter for a given JWT or API key.
Platforms such as Supabase productize this pattern with auth, storage, and client SDKs. The architectural bet is the same: the database is not a private implementation detail. It is part of the contract with clients.
That bet pays off often enough that founders should understand it on purpose, not stumble into it because a tutorial skipped the backend chapter.
When the pattern is enough
Your UI is mostly honest CRUD. Lists, detail pages, forms, and dashboards that create and update rows without exotic side effects fit well. If the hard part is showing the right rows to the right tenant, database enforced scoping is a feature, not a shortcut.
Rules compile to constraints and policies. Foreign keys, check constraints, unique indexes, and row level security can express who may insert what. If you can describe access control as "members of org X may touch rows where org_id equals X," you are in native territory for schema driven REST.
Speed matters more than API aesthetics. MVPs, internal tools, companion apps, and admin consoles often need to exist this month. Hand writing REST for twelve tables is waste if the schema is the source of truth anyway.
One client owns the contract. Mobile and web apps built by the same team can evolve with the schema. You are not publishing a versioned public API for strangers to depend on for years.
The team respects SQL. Migrations live in Git. Policies get reviewed. Someone can explain what the anon role can do. Without that discipline, you do not have an architecture. You have exposure.
If most of those bullets are true, REST from the database is not a compromise. It is the correct boundary.
When the pattern starts to hurt
Multi step workflows with compensating actions. Charging a card, reserving inventory, and writing audit rows should commit or roll back as a unit. You can push some of that into Postgres functions. Past a certain complexity, application services with explicit steps are easier to test and reason about than a nest of triggers.
The API shape should not mirror tables. Good public APIs aggregate, rename, and hide internal normalization. Customers should not see your join table because PostgREST happily exposed it. If stable JSON contracts matter more than schema purity, a dedicated service layer earns its keep.
Heavy integration glue. Webhooks from Stripe, Salesforce sync, email providers, and PDF generators want a place to live that is not a Postgres function invoked from a REST insert. Not because Postgres cannot run code. Because operations teams need logs, retries, and deploy cadence separate from migration windows.
Authorization stories that resist SQL. Attribute based rules, dynamic feature flags, and cross tenant admin impersonation often become unreadable policy soup. Middleware in a typed language with tests beats clever RLS when the rules change weekly.
Performance paths that need bespoke batching. Auto generated endpoints encourage generic queries. Hot paths sometimes need hand tuned SQL behind a narrow route. Fighting the generator on every optimization is a signal you outgrew the pattern.
A simple test founders can run
Describe the next three features on your roadmap as sentences. For each sentence, ask: "Could a careful policy on a table make this true?"
Examples that pass:
- "Org admins can invite users to their org."
- "Users see only projects they belong to."
- "Managers approve expenses over five hundred dollars."
Examples that fail:
- "When a subscription cancels, refund prorated amount, revoke seats, email finance, and schedule a win back campaign."
- "Merge two accounts without duplicate charges."
- "Expose v2 of checkout that hides legacy price fields forever."
Failures do not mean abandon Postgres. They mean add a service boundary where orchestration lives while Postgres stays the ledger.
Layers, not religions
The healthy stack is usually layered:
| Layer | Responsibility | Typical home |
|---|---|---|
| Client | UX, validation for humans | Web or mobile app |
| HTTP API | Stable contracts, orchestration | Custom service or edge functions |
| Data | Truth, constraints, scoped reads | Postgres with RLS |
| Platform | Auth, storage, hosting | Supabase, managed Postgres, etc. |
REST from the database collapses the first two rows for a while. That collapse is fine until features need orchestration or public API promises.
What is the difference between Postgres and PostgREST? stays useful here: Postgres remains the vault even when you add a custom API in front. PostgREST is one door. Your own service is another door. Pick the door based on who needs to walk through it.
Security is the hidden schedule item
Schema driven APIs fail in production when teams treat row level security as copy paste from docs. Every table needs a default deny story. Every new view needs a policy review. Service role keys must never ship to browsers.
Founders should budget time for a security pass as real as the design pass. Ask an engineer to attempt horizontal privilege escalation on staging. If they succeed in an afternoon, REST from the database is not enough yet. Policies are.
Versioning and coupling
Coupling clients to table names is acceptable early. It becomes expensive when external partners integrate or when mobile apps lag web deploys by months.
Mitigations that work without a full rewrite:
- Expose views instead of base tables to create a thin rename layer.
- Wrap sensitive writes in Postgres functions exposed as RPC endpoints.
- Introduce a BFF or edge function for the few routes that need stability while keeping reads schema driven.
The goal is incremental boundaries, not a big bang rewrite because someone read that PostgREST does not scale philosophically.
Team skills matter as much as product shape
A team strong in SQL and migrations will outship a team fighting an ORM on the same pattern. A team that hates reviewing policies will create incidents no matter how fancy the custom API looks.
If your hire plan is two front end engineers and no one who owns the database, schema driven REST is a risk multiplier. Either plan for database ownership or plan for a conventional API layer that encodes rules in code your team already reads daily.
Cost math founders actually care about
Custom APIs cost engineer weeks up front and maintenance forever. Schema driven REST costs policy and migration discipline up front and saves handler churn early.
Neither is free at scale. Connection pooling, query review, and index work land on both paths. The difference is where bugs appear. Misconfigured RLS shows up as data leaks. Misconfigured middleware shows up as 500s. Founders should pick the failure mode their stage can detect and fix.
Decision guide
Use REST from the database when:
- Tenants and roles map cleanly to row filters.
- Features are CRUD plus reports, not sagas.
- You control all clients and can migrate with the schema.
- Someone senior will own policies and migrations.
Add a custom API layer when:
- You sell an API as a product.
- Workflows span multiple systems with retries.
- Rules change faster than you want migration only deploys.
- You need human readable audit trails of business decisions outside SQL.
Start schema driven and peel off services when metrics or incidents tell you to. Starting with six microservices because adulthood is a vibe burns runway without teaching you what the product actually needs.
Proof of concept that reveals the truth
Build one feature that includes a write with a side effect you care about. Try implementing it as RLS plus a view or function. If the SQL stays readable after three edge cases, you are probably fine for the next quarter. If you are fighting triggers to send email, stop and draw a service box on the whiteboard.
Also measure time to add a column end to end. Schema driven wins when that path is minutes. Custom API wins when every column requires DTOs in three repos and a changelog for partners.
The founder takeaway
REST from the database is enough when your product truth already lives in Postgres and your rules fit constraints and policies. It stops being enough when orchestration, stable public contracts, or integration complexity become the product.
The pattern is not junior or senior. It is a boundary choice. Make it explicitly, review it when the roadmap adds payments or partnerships, and keep Postgres as the system of record either way.
Work with Kleto
I am James Cowan, a product engineer and the founder of Kleto. Kleto is a product engineering agency that ships production software from strategy through handoff. We help teams decide where REST from the database is enough, where to add a service layer, and how to keep Postgres honest as the product grows. If that matches your stack, contact Kleto and we will scope a sensible first step.