When is REST from the database enough?
Every early stage team hits the same fork. Stand up a custom API service and write handlers for each screen. Or 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 whether your product's rules live in tables and permissions, or in orchestration that should stay out of the database wire protocol.
My take: REST from the database is enough when tenant scoping, CRUD, and reports are the product and someone senior owns policies and migrations. Add a custom API layer when you sell an API, run multi system workflows with retries, or need stable JSON contracts that should not mirror table names.
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 choose 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 say "members of org X may touch rows where org_id equals X," you are in native territory.
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 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 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 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 home that is not a Postgres function invoked from a REST insert. Postgres can run code. Operations teams still 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 test founders can run in ten minutes
Describe the next three features on your roadmap as sentences. For each, 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.
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.
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 without a big bang rewrite
Coupling clients to table names is acceptable early. It gets expensive when external partners integrate or mobile apps lag web deploys by months.
Mitigations that work:
- 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 rewrite because someone read that PostgREST does not scale philosophically.
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. Pick based on who needs to walk through it.
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 nobody 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.
What I would do again
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.
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.
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.
Lessons:
- REST from the database is enough when product truth lives in Postgres and 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. Review it when the roadmap adds payments or partnerships.
- Keep Postgres as the system of record either way. Change the door, not the vault.
Work with Kleto
I am James Cowan, a product engineer and the founder of Kleto. Kleto 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.