Mastering API Patterns: BFF vs. Gateway vs. GraphQL (2026 Guide)
Introduction
In the early days of development, the frontend talked directly to the database (monoliths). Then came microservices, and suddenly our frontends had to make 15 HTTP requests just to render a "User Profile" page.
This introduced the "Chatty Interface" problem: high latency, over-fetching data, and complex frontend logic.
To solve this, architects developed several patterns to decouple the client from the server complexity. Today, we compare the titans: API Gateway, Backend for Frontend (BFF), and GraphQL.
1. The API Gateway Pattern
The API Gateway is a single entry point for all clients. It sits between the public internet and your internal microservices.
How it Works
The client sends a request to api.myapp.com/products. The Gateway routes this to product-service:8080, handles authentication, rate limiting, and caching, and returns the response.
- Pros: Centralized security, simple for clients (one URL).
- Cons: It becomes a "God Object." If the Mobile team needs a smaller payload but the Web team needs full details, the Gateway developer has to compromise, or the endpoint becomes bloated.
2. The Backend for Frontend (BFF) Pattern
The BFF pattern acknowledges that Mobile and Web clients have different needs.
- Mobile: Needs small distinct payloads to save battery and data. Bandwidth is unreliable.
- Web: Can handle larger payloads and more concurrent requests.
- Public API: Needs strict versioning and docs.
Instead of one "General Purpose API," you build specific API layers for each interface.
How it Works
- mobile-api.myapp.com -> Talks to internal Microservices -> Formats for iOS/Android.
- web-api.myapp.com -> Talks to internal Microservices -> Formats for React Dashboard.
Code Example: Implementing a BFF in NestJS
Here is how a BFF aggregates data from multiple internal services to send a "clean" response to a Mobile App.
1// mobile-bff.controller.ts 2@Controller('mobile/dashboard') 3export class MobileDashboardController { 4 constructor( 5 private readonly userService: UserService, 6 private readonly orderService: OrderService, 7 private readonly notifService: NotificationService 8 ) {} 9 10 @Get() 11 async getDashboard(@Headers('user-id') userId: string) { 12 // 1. Parallelize downstream calls to microservices 13 const [user, orders, unreadCount] = await Promise.all([ 14 this.userService.findById(userId), // Returns { id, name, email, address, preferences... } 15 this.orderService.getLastOrder(userId), // Returns full order object 16 this.notifService.getUnreadCount(userId) 17 ]); 18 19 // 2. Transform and Filter (The BFF Logic) 20 // The mobile app ONLY needs the name and last order status. 21 // We strip out 90% of the data to save bandwidth. 22 return { 23 greeting: `Hello, ${user.firstName}`, 24 status_card: { 25 last_order_id: orders.id, 26 status: orders.status, // e.g., "Shipped" 27 tracking_url: orders.trackingUrl 28 }, 29 badge_count: unreadCount 30 }; 31 } 32}
- Pros: Optimized performance for specific clients. The Frontend team can "own" the BFF and iterate fast without waiting for Backend teams to change core microservices.
- Cons: Code duplication. You might write similar logic in the Web BFF and Mobile BFF.
3. GraphQL (The "Client-Defined" Pattern)
GraphQL flips the model. Instead of the server defining what is returned, the client defines it.
How it Works
The server exposes a graph of all possible data. The client sends a query:
1query { 2 user(id: "123") { 3 firstName 4 orders(limit: 1) { 5 status 6 } 7 } 8}
- Pros: Zero over-fetching. Frontends are completely unblocked; they can query whatever they want without asking backend devs for new endpoints.
- Cons: Complexity. Preventing malicious users from requesting nested queries 100 levels deep (DoS attack) requires sophisticated complexity analysis and rate limiting. Caching is also much harder than REST.
Comparison Matrix: Which one to choose?
| Scenario | Recommended Pattern | Why? |
|---|---|---|
| Simple web app (Monolith) | Direct REST API | Don't overengineer. Keep it simple. |
| Enterprise Microservices | API Gateway | You need a centralized place for Auth, SSL, and Routing. |
| Separate Web & Mobile Teams | BFF (Backend for Frontend) | Allows mobile devs to optimize their API independently of web requirements. |
| Data-Heavy Dashboard | GraphQL | Dashboards often need random bits of data from 50 sources. GraphQL shines here. |
| Public Developer API | REST with Gateway | Public devs want standard, predictable REST endpoints, not a custom BFF. |
Conclusion
There is no "One Pattern to Rule Them All."
- Use an API Gateway to protect your microservices.
- Put a BFF behind that Gateway if your Mobile and Web apps are drifting apart in requirements.
- Use GraphQL if you have a highly interconnected data model and want to empower frontend autonomy.
In 2026, the most successful architectures often mix these: A generic API Gateway that routes traffic to specific BFFs (one of which might be a GraphQL server!).




