Your Database Is Already the Best API You'll Ever Write

PostgREST and Supabase prove that for CRUD operations, the database itself handles it better than your service layer.

Illustration for Your Database Is Already the Best API You'll Ever Write
database-is-api For decades we buried databases under layers of abstraction. Tools like PostgREST and Supabase show that PostgreSQL with Row-Level Security is often the best API you'll ever write. PostgREST, Supabase, database API, REST API, PostgreSQL, Row-Level Security, CRUD operations, API design

For decades, the industry taught us that exposing your database directly is a cardinal sin. Build a service layer. Write controllers. Abstract everything. But PostgREST and Supabase are proving that for CRUD operations, the database itself is often the best API you'll ever write.

TL;DR

Treat your database as a stable API. Use views and stored procedures to decouple apps from schema. Database stability enables application agility.

The dogma runs deep. Every architecture diagram I've seen in 30 years shows the database safely hidden behind layers of application code. "Never expose your database directly" is treated as immutable law. For complex business logic, it still holds. But here's what I've learned the hard way after building countless data-driven applications: for the 80% of operations that are straightforward CRUD, we've been writing boilerplate that the database could handle better.

The PostgREST Philosophy

PostgREST is a standalone web server that turns your PostgreSQL database directly into a RESTful API. Point it at a database, and it exposes your tables, views, and stored procedures as HTTP endpoints. No code generation. No ORM configuration.

The philosophy is simple: PostgreSQL already knows your schema. It knows your relationships. It knows your constraints. It has a robust permission system. Why duplicate all of that in application code?

According to PostgREST's documentation, three factors contribute to its speed. The server is written in Haskell using the Warp HTTP server. It delegates calculation to the database, which is already optimized for this work. There's no object-relational impedance mismatch because there are no objects - just HTTP in, SQL out, results back.

The result: subsecond response times for up to 2000 requests per second on modest hardware. That's not because the tool is magic. It's because every layer you remove is latency you don't pay.

The Supabase Validation

If PostgREST were just an interesting experiment, we could dismiss it. But Supabase built a billion-dollar company on this architecture. Their entire platform runs PostgREST as the API layer.

As Supabase's architecture documentation explains, their goal is to provide an architecture that any large-scale company would design for themselves. Then they provide tooling that makes it accessible to indie developers. Their litmus test: "Can a user run this product with nothing but a Postgres database?"

As Supabase's API documentation shows, the API is auto-generated from your database schema. Create a table, and it's immediately accessible through a fully functional, queryable API. No boilerplate code required. As you update your database, the changes are immediately accessible through your API.

This isn't a prototype pattern. This is production architecture serving real applications at scale.

Why the Old Dogma Existed

The "never expose your database" rule came from a real place. Early web applications did genuinely dangerous things:

Raw SQL in URLs. Applications would accept SQL fragments as query parameters and execute them directly. This was insane, and we rightfully stamped it out.

No authentication layer. Database credentials were sometimes exposed to clients, giving attackers direct access to everything.

Schema coupling. Change a column name, break every client. No versioning, no deprecation path.

Business logic in clients. Critical rules scattered across front-end applications where they couldn't be enforced.

These were real problems. But the solution - bury the database under layers of abstraction - was overcorrection. Modern tools solve these problems differently. PostgREST handles authentication via JWT and delegates authorization to PostgreSQL's Row-Level Security. Security is defined at the data layer.

Row-Level Security Changes Everything

The key innovation that makes database-as-API viable is Row-Level Security (RLS). PostgreSQL lets you define policies that control which rows each user can access:

-- Users can only see their own orders
CREATE POLICY user_orders ON orders
  FOR SELECT
  USING (user_id = current_user_id());

-- Admins can see everything
CREATE POLICY admin_orders ON orders
  FOR ALL
  USING (is_admin());

These policies are enforced at the database level. No matter how a query arrives - through PostgREST, through a direct connection, through a bug in your application - the rules apply. The database becomes the single source of truth for security.

This is fundamentally different from middleware authorization. In a traditional three-tier architecture, a bug in your API layer could expose data it shouldn't - I've seen this happen in production more times than I'd like to admit. With RLS, the database itself refuses to return unauthorized rows.

The 80/20 Architecture

Here's the pattern that's emerging: PostgREST handles 80% of operations while a custom API handles the remaining 20%. Your custom API also talks to PostgreSQL, but it handles business logic that doesn't belong in the database.

For CRUD operations - listing resources, fetching details, creating records, updating fields - the database is the API. For complex operations - multi-step workflows, external integrations, complex business rules - you write custom endpoints.

This is liberating. Instead of writing hundreds of nearly-identical controller methods, you write dozens of meaningful ones. When I was at MSNBC building content management systems, we spent enormous effort on exactly this kind of boilerplate. Instead of maintaining a mapping layer between domain objects and database schema, you embrace the schema directly.

Think about what a typical REST API controller does: validate input, transform it to a query, execute the query, transform the result, return it. PostgREST does all of this, but with validation defined in database constraints and transformation handled by PostgreSQL's type system. We've been reinventing the wheel with ORMs when the database already had good wheels.

Schema Versioning Without Service Layers

One argument for service layers is versioning. "What if I need to change the schema? My clients will break!" But PostgREST solves this through database schemas - the PostgreSQL kind.

Create a view that presents the old schema. Point v1 clients at that view. Create a new view for v2. The underlying tables can change freely. Views become your API contract.

-- V1 API: old field names
CREATE VIEW api_v1.users AS
  SELECT id, email, full_name as name FROM internal.users;

-- V2 API: new structure
CREATE VIEW api_v2.users AS
  SELECT id, email, first_name, last_name FROM internal.users;

This is versioning at the data layer instead of the application layer. It's simpler to maintain and doesn't require deploying new code when you want to change how old clients see data.

When This Pattern Doesn't Work

I'm not advocating for exposing every database directly. The pattern breaks down when:

Complex business logic. If creating an order requires inventory checks, payment processing, and email notifications - that's application logic. Don't try to cram it into stored procedures.

Multiple data sources. If a single API response combines data from PostgreSQL, a cache, and an external service - you need an orchestration layer.

Heavy transformation. If the data you return looks nothing like the data you store - computed fields, aggregations, format conversions - a service layer provides cleaner separation.

Rate limiting and quotas. Database connection limits are a blunt instrument. API gateways provide finer control.

The point isn't that every API should be database-direct. It's that many APIs shouldn't have as many layers as they do. This is the layer tax at work. We've added so much abstraction that simple operations become complex.

Database-as-API Decision Matrix

Before choosing your architecture, score your use case. Check all that apply—the database handles CRUD; custom code handles complexity.

Architecture Score: 0
Check applicable items to see recommendation

The Developer Experience Revolution

What strikes me most about this approach is how it changes development velocity. Instead of:

  • Design database schema
  • Write migration
  • Write model classes
  • Write controller
  • Write validation logic
  • Write serialization logic
  • Write tests for all of the above

You get:

  • Design database schema
  • Write migration
  • Define RLS policies
  • Done

The auto-generated OpenAPI documentation means your API is self-documenting. The database constraints enforce validation. The type system handles serialization. You're not writing less code because you're lazy. You're writing less code because the right layer is handling the work.

The Trust Issue

When I explain this pattern, the most common pushback is trust. "I don't trust the database to handle my API." But consider what you're trusting instead:

Your hand-written validation logic. Your ORM's query generation. Your serialization library's type handling. Your middleware's authentication.

PostgreSQL has been handling data access control, query optimization, and type conversion for decades. It's been battle-tested against more edge cases than your application framework ever will be. The question is which software has the better track record.

Database-as-API Decision Matrix

This interactive assessment requires JavaScript. The checklist below is still readable.

Before choosing your architecture, score your use case. Check all that apply—the database handles CRUD; custom code handles complexity.

Assessment
Score: 0
Complete the assessment above

The Bottom Line

The database-as-API pattern isn't right for everything, but it's right for more than our industry admits. For straightforward CRUD operations - the majority of endpoints in most applications - exposing the database through PostgREST or Supabase is simpler and more maintainable.

The old dogma served its purpose. It protected us from real mistakes. But tools have evolved. PostgreSQL with Row-Level Security is not the database we were protecting clients from in 2005. Sometimes the best abstraction is no abstraction at all.

"The question is which software has the better track record."

Sources

Architecture Review

Wondering if your service layer is adding value or just complexity? Get perspective on simplifying your stack.

Get Assessment

Simpler Than I Think?

If there's a straightforward solution I'm overcomplicating, I genuinely want to hear it.

Send a Reply →