DEV Community

Cover image for Why You Should Never Use a Database as an API
Doogal Simpson
Doogal Simpson

Posted on • Originally published at doogal.dev

Why You Should Never Use a Database as an API

TL;DR: Sharing databases across team boundaries bypasses service interfaces, turning database schemas into public APIs. This tightly couples services, halts schema migrations, and stalls development. To maintain architectural agility, I always advise teams to hide their databases behind well-defined APIs, decoupling internal implementation from external integration.

I'm no big fan of Jeff Bezos, but his early Amazon API mandate was absolute gold. The story goes that he sent a company-wide email warning that if anyone used another team's database directly as an API, they would be fired. While threatening to sack your engineering team is a bit extreme, the technical wisdom behind it is something I advocate for constantly. I see teams take this shortcut all the time, only to watch it blow up in their faces later. Here is my breakdown of why we need to stop doing this and how we can build better boundaries.

Why is direct database sharing bad for microservices?

In my experience, sharing databases directly across services destroys boundaries because it turns your internal storage schema into your public interface. When external teams query your database directly, you lose the ability to rename columns, change data types, or refactor your schema without breaking their systems.

I like to think of this using a physical analogy. Imagine you are setting up a smart home. If your smart lights plug into standard wall outlets, they work perfectly because they rely on a stable, standardized interface. But if those lights required you to splice raw copper wires directly into your home's main electrical panel, you could never upgrade your wiring or swap a circuit breaker without risking a blackout.

When Team B queries Team A's database directly, they bypass the API layer. The database schema—the raw copper wire—becomes the interface. If Team A wants to rename a column from user_id to account_id or migrate from PostgreSQL to MongoDB, they are stuck. They cannot make changes because they no longer own their data model.

How do API interfaces solve the database coupling problem?

From what I've seen, APIs solve the coupling problem by introducing a layer of abstraction between how data is stored and how it is consumed. By exposing data through HTTP endpoints, gRPC, or event streams, the owning team can freely modify their database schema as long as the API payload remains contract-compliant.

Let's say you have a billing service that needs data from a user profile service. Instead of writing a direct SQL query against the users table, the billing service should call GET /users/{id}.

Behind that HTTP endpoint, the profile service can restructure its tables, split a single table into three normalized tables, or even change its caching strategy. As long as the JSON response format does not break, the billing service does not know and does not care. This separation of concerns keeps teams operating independently and moving fast.

What are the trade-offs of direct database access versus API integration?

I always warn developers that while direct database access offers lower initial latency and faster setup, it leads to tight coupling and unmaintainable technical debt. API integration requires upfront design and introduces minor network overhead but ensures team autonomy, independent scaling, and safe schema evolution.

Metric / Feature Direct Database Access Service Interface (API)
Coupling Level Extremely Tight (Schema is public) Loose (Schema is private)
Development Speed Fast initially, degrades rapidly over time Consistent, predictable long-term velocity
Schema Migrations High-risk (will break external consumers) Safe (hidden behind abstraction layer)
Security & Auditing Difficult (requires database-level permissions) Simple (handled at the application layer)
Technology Choice Locked into a single database technology Free to choose different DBs per service

How do we transition away from shared databases?

To transition away from a shared database model, I recommend establishing clear data ownership and creating wrappers around legacy data paths. Teams must stop writing cross-schema joins, build lightweight API wrappers over the data, and migrate consumers to the new endpoints.

If you are currently stuck in a "shared database culture," I do not expect you to rewrite everything overnight. Start by treating the legacy tables as deprecated contracts. Write a thin service layer that queries those tables and exposes the data via standard HTTP endpoints. Have other teams transition their read queries to these new endpoints one by one. Once the direct database queries drop to zero, you finally own your schema again and can begin refactoring with confidence.

FAQ

Is sharing a database schema ever acceptable?

I only allow it within the boundary of a single, deployable microservice owned by one team. If multiple teams or services deploy independently but touch the same database schema, you have a distributed monolith, not microservices.

Does this rule apply to read-only database replicas?

Absolutely. Even if another team is only reading from a read replica of your database to avoid performance degradation, they are still coupled to your underlying schema. If you rename a column on the primary database, the replica schema changes and their queries will fail.

How do we handle high-performance reporting without direct database access?

Instead of querying another team's database directly for reporting, I recommend using asynchronous event streaming (like Kafka or RabbitMQ) to replicate the necessary data to a dedicated reporting database, or utilizing an ETL pipeline to move data to a central data warehouse.

Top comments (0)