Mid-level 10 min · March 06, 2026

REST vs SOAP vs GraphQL — Unbounded Queries Crash DB

Database connections spike to max with 'too many clients already' when GraphQL lacks depth limits.

N
Naren · Founder
Plain-English first. Then code. Then the interview question.
About
 ● Production Incident 🔎 Debug Guide
Quick Answer
  • REST is resource-based, CRUD over HTTP with stateless operations
  • SOAP is protocol-based, uses XML envelopes and strict WSDL contracts
  • GraphQL is query-based, single endpoint with client-defined responses
  • REST: ~30% overhead from over-fetching; GraphQL eliminates this but adds query cost analysis
  • Production risk: GraphQL without complexity analysis crashes databases — always set depth and cost limits
  • Biggest mistake: choosing SOAP for a public mobile API — heavy XML payload, slow onboarding, poor developer experience
Plain-English First

Imagine ordering food. SOAP is like calling a restaurant on a landline — there's a strict script you must follow, and the restaurant confirms every detail back to you in writing. REST is like ordering via a website menu — you pick what you want from a fixed set of pages, and it just works. GraphQL is like texting a personal chef — you describe exactly what you want on your plate, no more, no less, and they deliver precisely that. Same goal (getting food), wildly different experiences.

Every application that talks to another application — a mobile app hitting a backend, a payment gateway, a dashboard pulling live data — uses an API. The style of that API determines how fast you can build, how well it scales, and how painful it is to change later. REST, SOAP, and GraphQL are the three dominant API styles in the industry, and choosing the wrong one is a silent tax that compounds over years.

The problem is that most tutorials describe these three as if they're interchangeable tools with different syntax. They're not. Each one was invented to solve a different pain point. SOAP was built when web services needed enterprise-grade reliability and formal contracts. REST emerged to make the web itself the platform — stateless, cacheable, universally accessible. GraphQL was created by Facebook because REST's rigid endpoint model broke down at the scale of a billion-user social graph.

By the end of this article you'll be able to explain the architectural philosophy behind each style, write and call real API examples in all three, articulate the performance and maintainability trade-offs in a team discussion, and — most importantly — confidently answer 'which should we use?' on your next project without Googling it.

What is REST vs SOAP vs GraphQL?

REST vs SOAP vs GraphQL is a core concept in CS Fundamentals. Rather than starting with a dry definition, let's see it in action and understand why it exists.

Each style emerged from a specific gap. SOAP (2000) gave enterprises a formal contract with WSDL and WS-Security — but wrapped every message in heavy XML. REST (2000, by Roy Fielding) turned HTTP into a stateless resource system, caching everything by default. GraphQL (2015, by Facebook) solved the problem of over- and under-fetching by letting clients define exactly what they need. The differences aren't just syntax — they reflect fundamentally different trade-offs in coupling, performance, and operational complexity.

Production teams often miss that REST isn't a protocol — it's a set of constraints. Most 'REST APIs' are just HTTP CRUD. That's fine if you know the trade-off: you lose hypermedia-driven discoverability. SOAP forces a contract, but that contract becomes a coordination bottleneck. GraphQL gives clients power but shifts complexity to resolvers. Don't choose based on hype — choose based on your data shapes and client diversity.

The real mistake is thinking REST is just 'CRUD over HTTP' without understanding the uniform interface constraint. If you treat it as just a style for mapping resources to URLs, you miss the caching and statelessness benefits that make it scale.

ForgeExample.javaCS FUNDAMENTALS
1
2
3
4
5
6
7
8
// TheCodeForgeREST vs SOAP vs GraphQL example
// Always use meaningful names, not x or n
public class ForgeExample {
    public static void main(String[] args) {
        String topic = "REST vs SOAP vs GraphQL";
        System.out.println("Learning: " + topic + " 🔥");
    }
}
Output
Learning: REST vs SOAP vs GraphQL 🔥
Forge Tip:
Type this code yourself rather than copy-pasting. The muscle memory of writing it will help it stick.
Production Insight
REST's stateless constraint means every request carries full context — that adds ~200 bytes per request in headers.
In microservices with 10 hops per request, that's 2KB of overhead just for routing.
Rule: use HTTP/2 multiplexing to amortize header overhead across streams.
Key Takeaway
REST is not just CRUD over HTTP — it's a set of architectural constraints.
SOAP is a protocol, not a style — it defines message format and contract.
GraphQL gives power to clients but requires server-side governance.

REST: Principles and Production Reality

REST (Representational State Transfer) is not a standard but an architectural style defined by Roy Fielding in 2000. It relies on stateless client-server communication, caching, and a uniform interface. In practice, most 'REST APIs' are just HTTP CRUD endpoints. True REST requires hypermedia (HATEOAS), which almost no one implements. That's okay — as long as you understand the trade-off.

Production REST is about resource modeling. Each endpoint represents a noun (e.g., /users, /orders), and HTTP methods map to actions. The real power is cacheability — HTTP caches at proxies, CDNs, and browsers can significantly reduce server load.

Where REST breaks down is when the client needs custom data shapes. You end up with either over-fetching (getting too much data) or under-fetching (needing multiple requests). That's where GraphQL steps in.

REST's big trap: versioning. Without an explicit versioning strategy, changing a response field forces all mobile clients to update. Always put a version in the URL or accept header, and document deprecation timelines.

Another trap: assuming caching works out of the box. You must explicitly set Cache-Control and ETag headers. Many teams forget, then wonder why their CDN serves stale data. Test caching behavior in staging with curl -I and verify headers.

If you're building a dynamic dashboard with REST, you'll feel the over-fetching pain immediately. The desktop browser might render fine, but your mobile app on a 3G connection will suffer. That's when you consider GraphQL for that specific use case.

io/thecodeforge/controller/UserController.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package io.thecodeforge.controller;

import io.thecodeforge.model.User;
import io.thecodeforge.repository.UserRepository;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/users")
public class UserController {

    private final UserRepository repository;

    public UserController(UserRepository repository) {
        this.repository = repository;
    }

    @GetMapping
    public List<User> getAllUsers() {
        return repository.findAll();
    }

    @GetMapping("/{id}")
    public User getUserById(@PathVariable Long id) {
        return repository.findById(id)
                .orElseThrow(() -> new ResourceNotFoundException("User not found"));
    }

    @PostMapping
    public User createUser(@RequestBody User user) {
        return repository.save(user);
    }
}
Versioning Trap
No versioning strategy + mobile clients = heartbreak. Every field change becomes a coordinated release. Use /v1/ from day one, even if you think you'll never change the API.
Production Insight
REST without versioning is a silent contract breaker.
Changing a response field breaks all mobile clients until they update.
Rule: version your API (e.g., /v1/users) and never remove a field without a deprecation cycle.
Key Takeaway
REST wins when your clients are predictable and caching matters.
It loses when clients need flexible, aggregated data.
The uniform interface is both its strength and its limitation.

SOAP: When You Need a Contract

SOAP (Simple Object Access Protocol) uses XML envelopes and a WSDL (Web Services Description Language) to define every message. It's heavy — XML parsing adds latency and payload size bloat. But it comes with built-in error handling (SOAP Faults), security (WS-Security), and transaction support.

In production, SOAP is used where compliance and formal contracts are non-negotiable: banking, insurance, government systems. The WSDL acts as a legal agreement — both sides know exactly what to send and receive.

SOAP's downside is flexibility. Changing a WSDL requires coordinated deployments across all consumers. It's also slow — a simple request can be 10KB in XML overhead. You don't use SOAP for a mobile app.

If you're forced to expose a SOAP service to modern clients, consider a protocol translation gateway that converts SOAP to REST or GraphQL on the fly. That buys you time while the legacy contract remains intact.

Performance tip: XML parsing is CPU-bound. At scale, offload parsing to a separate gateway or use a binary XML format like Fast Infoset if the other end supports it.

One more thing: never assume WSDL is self-documenting for external teams. Always provide a human-readable contract alongside the WSDL.

soap-request.xmlXML
1
2
3
4
5
6
7
8
9
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/"
                  xmlns:web="http://io.thecodeforge/webservice">
   <soapenv:Header/>
   <soapenv:Body>
      <web:GetUser>
         <web:userId>123</web:userId>
      </web:GetUser>
   </soapenv:Body>
</soapenv:Envelope>
Bridging Legacy
Wrap a SOAP backend with a REST facade using Camel or Spring Integration. Your mobile apps never touch XML, and the legacy contract stays intact until you rewrite.
Production Insight
SOAP's XML parsing is CPU-intensive — 10x more than JSON for the same data.
At 1000 req/s, that extra parsing cost adds ~50ms latency per request.
Rule: if you need SOAP, offload XML parsing to a separate gateway or use HTTP compression.
Key Takeaway
SOAP is for B2B integrations where contract compliance matters more than speed.
Never expose SOAP to mobile or browser clients.
Use a protocol converter (e.g., SOAP-to-REST gateway) to bridge legacy systems.

GraphQL: Flexibility vs Complexity

GraphQL, created by Facebook in 2015, lets clients specify exactly the data they need. A single endpoint accepts queries, mutations, and subscriptions. This eliminates over-fetching and under-fetching. But it shifts complexity to the server.

The server must resolve queries that can be arbitrarily deep and wide. Without proper guards, a client can craft a query that does exponentially more work than intended. This is the N+1 problem on steroids — each level of nesting can multiply database calls.

Production GraphQL requires
  • Query complexity analysis (cost per field)
  • Depth limiting
  • DataLoader for batching
  • Persisted queries for untrusted clients

GraphQL shines for dashboard applications and mobile apps where network round-trips are expensive. But it adds operational overhead — you need a schema registry, monitoring per field, and resolver profiling.

One pattern that works well: expose a GraphQL endpoint for read-heavy dashboards, but keep REST for simple CRUD mutations. Don't force everything through one style.

And here's a tip: persist queries for untrusted clients. It closes the door on ad-hoc query attacks entirely.

schema.graphqlGRAPHQL
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
type Query {
  user(id: ID!): User
  posts(limit: Int): [Post]
}

type User {
  id: ID!
  name: String
  email: String
  posts: [Post]  # Danger: this can cause N+1
}

type Post {
  id: ID!
  title: String
  comments: [Comment]
}

type Comment {
  id: ID!
  text: String
  author: User
}
N+1 Ambush
That innocent-looking posts: [Post] field in the User type? Without DataLoader, fetching 100 users triggers 100 additional queries for posts. Always batch relation resolvers.
Production Insight
A single GraphQL query can trigger 1000+ database queries if resolvers don't batch.
This is the #1 production incident for teams new to GraphQL.
Fix: Use DataLoader or a similar batching layer — it's not optional. It's mandatory for production.
Key Takeaway
GraphQL gives clients power but you must add server-side guardrails.
Without complexity limits, any client can DoS your database.
GraphQL is best when the client needs custom data shapes and you can invest in resolver optimisation.

Choosing an API Style: A Decision Framework

Stop choosing an API style based on hype or what your friend is using. The decision should come from your constraints:

  • Client diversity: If you have mobile, web, and third-party clients, GraphQL reduces the number of endpoints and lets each client fetch exactly what it needs. But you pay in server complexity.
  • Caching requirements: REST is trivial to cache at every layer (CDN, browser, server). GraphQL responses are harder to cache because queries vary. If your data changes rarely and you need fast reads, REST wins.
  • Contract strictness: If you have multiple teams or external partners that must adhere to a fixed contract, SOAP's WSDL is actually a feature, not a bug. But you'll move slowly.
  • Performance budget: SOAP adds ~200% overhead per request due to XML and envelope wrapping. REST with JSON adds ~30%. GraphQL can be as lean as 1 query per screen, but resolver performance varies.

The rule of thumb: if you can't clearly articulate why you chose one style over another, you probably chose wrong. Take the time to map your constraints — it's cheaper than a migration later.

io/thecodeforge/enums/ApiStyle.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package io.thecodeforge.enums;

public enum ApiStyle {
    REST_CACHING,
    SOAP_CONTRACT,
    GRAPHQL_FLEXIBLE,
    WEBSOCKET_REALTIME;

    public static ApiStyle choose(boolean needsCaching, boolean needsFormalContract,
                                   boolean clientDiverse, boolean simpleCrud) {
        if (needsFormalContract) return SOAP_CONTRACT;
        if (simpleCrud && needsCaching) return REST_CACHING;
        if (clientDiverse) return GRAPHQL_FLEXIBLE;
        // fallback
        return REST_CACHING;
    }
}
Style Trade-off Map
  • REST: low cost to read, high cost to change response shape.
  • SOAP: high cost to write client, low ambiguity in contract.
  • GraphQL: low cost to query, high cost to secure and optimise.
Production Insight
The worst API decision is picking one style for the whole organization.
You end up with SOAP on mobile and GraphQL for internal microservices — chaos.
Rule: choose a default for external APIs and allow alternatives only with explicit justification.
Key Takeaway
There's no universal 'best' API style — only best for your constraints.
REST for caching, SOAP for compliance, GraphQL for flexibility.
Don't mix styles in the same API surface without clear boundaries.
Quick API Style Decision
IfHigh performance caching required (CDN, browser)
UseChoose REST
IfFormal contract needed (B2B, compliance)
UseChoose SOAP
IfMultiple clients with different data needs
UseChoose GraphQL
IfSimple CRUD, single client type
UseChoose REST
IfReal-time / bidirectional communication
UseConsider WebSockets or GraphQL subscriptions

Migrating Between Styles and Coexistence Patterns

In the real world, you'll rarely start from scratch. Most teams inherit a system built in one style and need to introduce another. The key is to isolate the styles at the API gateway, not within the same codebase.

A common pattern is the strangler fig: expose a new GraphQL endpoint alongside your existing REST API, and gradually migrate clients. The gateway routes requests based on either path prefix (e.g., /graphql vs /api/v1) or content negotiation headers.

Another pattern: use a protocol bridge. If your SOAP backend is rock-solid, wrap it with a RESTful proxy that translates JSON requests into SOAP envelopes. This gives you time to rewrite if needed, without forcing clients to change.

Performance tip: when running multiple styles, monitor the gateway's resource usage. XML parsing for SOAP can saturate CPU faster than JSON or GraphQL resolvers.

Authentication and rate limiting should be centralised at the gateway, not duplicated per style. Use a single OAuth 2.0 flow that applies to all routes.

One thing most teams forget: when you migrate, you'll likely have two versions of the same data source. Ensure consistency through a single source of truth or reconciliation jobs.

gateway-routes.ymlYAML
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# TheCodeForge API Gateway Configuration
routes:
  - path: /api/v1/**
    style: REST
    upstream: http://rest-backend:8080
  - path: /graphql
    style: GraphQL
    upstream: http://graphql-server:4000
  - path: /soap/**
    style: SOAP
    upstream: http://soap-legacy:9090
    transform:
      request: json-to-xml
      response: xml-to-json
Gateway First
Don't let each style implement its own auth. Centralise in the gateway. One JWT validation, one rate limiter, one logging format. Your operations team will thank you.
Production Insight
Running REST and GraphQL side-by-side without an API gateway leads to duplicated auth logic.
Each style may have its own rate-limiting and caching — that's more surface area to secure.
Rule: centralize authentication, rate limiting, and logging in the gateway regardless of API style.
Key Takeaway
Migration is about isolation: new style, new endpoint, same gateway.
Never mix styles in the same controller — it becomes untestable.
Use strangler fig or protocol bridge — both preserve existing clients until they're ready to move.

API Security: Common Pitfalls Across Styles

Security isn't an afterthought — it's baked into the style choice. Each API style introduces unique attack surfaces.

REST: The biggest risk is insecure direct object references (IDOR). A client requests /users/123 and gets another user's data if authorization is missing. Also, lack of rate limiting can lead to brute force. REST's statelessness forces you to validate every request.

SOAP: WS-Security is powerful but misconfigured more often than not. Replay attacks, XML injection, and oversized payloads are common. The WSDL can leak internal service details if exposed publicly.

GraphQL: Introspection exposes the full schema — an attacker can map all types and fields. Without depth and cost analysis, any authenticated client can craft a denial-of-service query. Field-level authorization is tricky because the same resolver may return data for users with different permission levels.

Production rule: never expose GraphQL introspection in production unless behind a VPN. Always implement field-level authorization guards in resolvers, not just in the schema.

A common production mistake: allowing introspection in production behind a VPN, thinking it's safe. It's not. One leak of a VPN credential and the attacker has your entire schema map.

io/thecodeforge/graphql/UserResolver.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package io.thecodeforge.graphql;

import io.thecodeforge.model.User;
import io.thecodeforge.repository.UserRepository;
import org.springframework.stereotype.Component;

@Component
public class UserResolver implements GraphQLQueryResolver {

    private final UserRepository userRepository;
    private final AuthContext authContext;

    public UserResolver(UserRepository userRepository, AuthContext authContext) {
        this.userRepository = userRepository;
        this.authContext = authContext;
    }

    public User user(String id) {
        // Field-level authorization: only return the user if the caller is that user
        if (!authContext.getUserId().equals(id)) {
            throw new AuthorizationException("Access denied");
        }
        return userRepository.findById(id).orElseThrow(() -> new UserNotFoundException(id));
    }
}
Production Trap
Introspection queries in production give attackers a full map of your GraphQL schema. Without query complexity limits, they can craft a deeply nested query that triggers a database meltdown — even with authentication.
Production Insight
GraphQL introspection in production is an open invitation to attackers.
One query can map your entire data model and identify every field.
Rule: disable introspection in production and use a schema registry for internal teams.
Key Takeaway
REST: IDOR and rate limiting. SOAP: WS-Security misconfiguration and replay attacks. GraphQL: introspection leaks and query-based DoS.
Always handle authorization at the field/resource level — style doesn't replace it.

Testing Strategies for REST, SOAP, and GraphQL

Each API style demands different testing approaches. You can't test a GraphQL resolver the same way you test a REST controller.

REST: Unit test controllers with mocked services. Integration test by starting an embedded server and hitting endpoints. Use contract tests with Spring Cloud Contract or Pact to ensure client and server agree on request/response shapes. Pay attention to status codes and error bodies.

SOAP: SoapUI or Postman with WSDL import. Validate XML schemas and SOAP faults. Test with different namespace combinations — mismatches cause hard-to-debug failures. Use performance tests with large payloads to catch XML parsing bottlenecks.

GraphQL: Test resolvers in isolation with mock data loaders. Write integration tests that execute queries against the full schema. Use persisted queries for critical flows to ensure queries don't change unexpectedly. Test both positive cases and malicious queries (complexity attacks, null injection).

Static analysis: Use tools like Spectral (REST), SOAPSonar (SOAP), and GraphQL Inspector to enforce style-specific rules in CI.

Don't forget to test the gateway if you use multiple styles. A wrong route config can silently serve stale data or leak internal endpoints.

And here's a counterintuitive tip: when testing REST, test for missing headers, not just wrong responses. Missing Cache-Control can cause a CDN cache stampede.

io/thecodeforge/graphql/UserResolverTest.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package io.thecodeforge.graphql;

import io.thecodeforge.model.User;
import io.thecodeforge.repository.UserRepository;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;

class UserResolverTest {

    @Test
    void shouldReturnUserWhenAuthorized() {
        UserRepository repo = mock(UserRepository.class);
        AuthContext auth = () -> "user-1";
        UserResolver resolver = new UserResolver(repo, auth);
        
        User expected = new User("user-1", "Alice");
        when(repo.findById("user-1")).thenReturn(Optional.of(expected));
        
        User result = resolver.user("user-1");
        assertEquals("Alice", result.getName());
    }

    @Test
    void shouldThrowWhenUnauthorized() {
        UserRepository repo = mock(UserRepository.class);
        AuthContext auth = () -> "user-2";
        UserResolver resolver = new UserResolver(repo, auth);
        
        assertThrows(AuthorizationException.class, () -> resolver.user("user-1"));
    }
}
Senior testing tip
For GraphQL, always test with the same DataLoader batching you use in production. A test that bypasses the loader might pass locally but fail under real load when queries aren't batched.
Production Insight
REST tests often miss versioning: if you add a new field, old clients may break if they expect a strict schema.
GraphQL tests often miss complex queries — only test simple cases, then a client hits a deeply nested query and explodes.
Rule: include stress tests with worst-case queries in your CI pipeline for every API style.
Key Takeaway
REST: test versioning and error codes. SOAP: test XML schema validation. GraphQL: test resolver batching, complexity limits, and field-level authorization.
Always test the failure paths — not just the happy path.

API Performance: Measuring Latency, Throughput, and Cost

Choosing an API style has direct performance implications that show up in production metrics. Latency, throughput, and operational cost vary significantly.

Latency: REST with JSON typically adds ~30% overhead over raw data due to HTTP framing and serialisation. SOAP adds ~200% due to XML envelope and namespace parsing. GraphQL can be faster per request (less data transferred) but resolver execution can add latency if not optimised with batching.

Throughput: REST handles high concurrent traffic well because of statelessness and caching. A well-cached REST endpoint can serve thousands of requests per second on a single instance. SOAP struggles — XML parsing is CPU-bound; at 500 req/s you'll likely need to scale horizontally. GraphQL throughput depends on query complexity; a simple query can match REST, but a deep nested query can tank throughput by orders of magnitude.

Cost: REST is cheapest to operate — simple infrastructure, caching reduces load. SOAP costs more per request due to CPU and bandwidth. GraphQL introduces operational complexity — you need resolver profiling, schema governance, and potentially a gateway.

Real-world benchmark: In a typical e-commerce API, a REST product endpoint returns ~1.2KB of JSON. GraphQL can reduce that to 300 bytes (75% reduction). But the resolver for that GraphQL query might need to fetch from 3 microservices — without DataLoader, that's 3 sequential HTTP calls, adding 150ms latency. With batching, it's one aggregation call, 50ms.

Always profile with your actual data shapes before committing to a style. What works for one team might be disastrous for another.

benchmark.shBASH
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# TheCodeForgeAPI latency benchmark
# Measure REST vs GraphQL vs SOAP with real payloads

echo "=== REST benchmark ==="
curl -o /dev/null -s -w 'REST: %{time_total}s\n' \
  -H "Accept: application/json" \
  https://api.thecodeforge.io/io/products?limit=10

echo "=== GraphQL benchmark ==="
curl -o /dev/null -s -w 'GraphQL: %{time_total}s\n' \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"query":"{ products(limit:10) { id name price } }"}' \
  https://api.thecodeforge.io/graphql

echo "=== SOAP benchmark ==="
curl -o /dev/null -s -w 'SOAP: %{time_total}s\n' \
  -H "Content-Type: text/xml" \
  -d '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://io.thecodeforge/webservice"> <soapenv:Body> <web:GetProducts><web:limit>10</web:limit></web:GetProducts> </soapenv:Body> </soapenv:Envelope>' \
  https://api.thecodeforge.io/soap/products
Output
REST: 0.045s
GraphQL: 0.052s
SOAP: 0.134s
Profit from profiling
Don't rely on benchmarks from blog posts — test with your own data shapes and query patterns. A decision based on production profiling beats a decision based on hype every time.
Production Insight
Caching makes REST 10x cheaper per request at scale vs uncached REST or GraphQL.
SOAP's XML parsing cost is often overlooked — it increases CPU usage by 30-50% at peak.
Rule: for high-throughput systems, measure CPU per request before choosing a style.
Key Takeaway
REST is cheapest and most cacheable; SOAP is expensive but contract-strong; GraphQL is flexible but operationally heavy.
Always profile with your real data before committing.
The cheapest API is the one you don't have to rewrite — choose for the long term.
● Production incidentPOST-MORTEMseverity: high

GraphQL Query Explodes Database Connection Pool

Symptom
Database connections spike to max and then errors pile up: 'too many clients already'. The API returns 500 with no obvious pattern.
Assumption
GraphQL is safe because the frontend only requests what it needs. We assumed no single query could cause a database meltdown.
Root cause
No depth limit or query cost analysis was configured. A client sent a query that requested 50 posts, each with comments, and each comment with author details, creating thousands of database round-trips. The resolver executed N+1 queries per level.
Fix
Implemented query complexity limits (max cost = 1000), depth limiting (max 5 levels), and replaced naive resolvers with batching (DataLoader). Also added connection pooling with a limit of 100 and a queue for overflow.
Key lesson
  • Always enforce query complexity and depth limits on public GraphQL endpoints.
  • Use DataLoader or similar batching to prevent N+1 queries in GraphQL resolvers.
  • Monitor database connection usage per query — not just per endpoint.
Production debug guideSymptom → Action for common failures in REST, SOAP, and GraphQL6 entries
Symptom · 01
Mobile app shows stale data despite hitting REST endpoint
Fix
Check HTTP cache headers (Cache-Control, ETag). REST relies on explicit caching — if missing, CDN or browser may serve stale responses.
Symptom · 02
SOAP response returns 'SOAP-ENV:Client' error
Fix
Validate the SOAP envelope structure against the WSDL using a tool like SoapUI. Most often a missing namespace or incorrect element order.
Symptom · 03
GraphQL query returns 'Query complexity limit exceeded'
Fix
Reduce the number of nested fields or use pagination. Check server-side complexity configuration and adjust if needed.
Symptom · 04
REST endpoint returns 405 Method Not Allowed
Fix
Verify that the HTTP method (GET, POST, PUT, DELETE) matches the resource's allowed methods. Common: using POST for read-only operations.
Symptom · 05
GraphQL resolver throws 'Cannot return null for non-nullable field'
Fix
Inspect the resolver logic — a parent resolver returned null for a required child field. Ensure your data fetching handles missing relations gracefully, or make the field nullable in the schema.
Symptom · 06
SOAP request times out after 30 seconds
Fix
Check the service-side processing time. SOAP often involves transactional operations that lock database rows. Use WSDL extensions to specify a timeout, or refactor long-running operations into asynchronous patterns.
★ API Performance Troubleshooting Cheat SheetQuick commands and fixes for performance issues across REST, SOAP, and GraphQL APIs.
REST API response time > 500ms
Immediate action
Identify the slowest endpoint in logs. Use curl with -w '%{time_total}'.
Commands
curl -o /dev/null -s -w 'Total: %{time_total}s\n' https://api.example.com/users
tail -n 100 /var/log/nginx/access.log | awk '{print $NF, $7}' | sort -rn | head
Fix now
Add database indexing on the most-queried columns and implement response caching.
SOAP response times are inconsistent+
Immediate action
Check if SSL handshake is the bottleneck by measuring time to first byte.
Commands
curl -o /dev/null -s -w 'connect: %{time_connect} starttransfer: %{time_starttransfer} total: %{time_total}\n' https://soap.example.com/service
openssl s_client -connect soap.example.com:443 -servername soap.example.com </dev/null 2>/dev/null | grep 'SSL handshake'
Fix now
Enable HTTP keep-alive and consider certificate caching or OCSP stapling.
GraphQL query takes > 1s but resolver logs show fast individual queries+
Immediate action
Check if the query is running N+1 queries due to missing batching.
Commands
Enable query logging with Apollo Studio or similar, look for repeated identical SQL queries.
SELECT * FROM pg_stat_activity WHERE query NOT LIKE '%autovacuum%' ORDER BY query_start;
Fix now
Implement DataLoader for all relation resolvers and add query complexity limits.
REST API cache hit ratio is below 10%+
Immediate action
Audit Cache-Control headers on all GET endpoints. Many teams forget to set max-age or ETag.
Commands
curl -sI https://api.example.com/users | grep -i 'cache\|etag\|last-modified'
grep -rn 'Cache-Control' src/main/java/io/thecodeforge/controller/
Fix now
Apply consistent caching headers: set Cache-Control: public, max-age=60 on all read-only endpoints, and include ETag for conditional requests.
REST vs SOAP vs GraphQL Comparison
DimensionRESTSOAPGraphQL
Key IdeaResources (nouns)Actions via envelopesClient-defined queries
Data FormatJSON (or XML)XML onlyJSON (query and response)
CachingBuilt-in (HTTP cache)None (application-level)Difficult (query-specific)
Performance OverheadLow (~30% over raw data)High (~200% due to XML)Varies (query complexity)
ContractInformal (OpenAPI optional)Formal (WSDL)Schema (SDL) but no runtime contract
Best ForPublic APIs, mobile backendsEnterprise B2B, financeComplex UIs, data dashboards
Worst ForComplex data relationshipsPublic mobile APIsSimple CRUD with caching

Key takeaways

1
You now understand what REST vs SOAP vs GraphQL is and why it exists
2
You've seen it working in a real runnable example
3
Practice daily
the forge only works when it's hot 🔥
4
REST is best for cacheable, stateless CRUD operations over HTTP
5
SOAP is for enterprise B2B where formal contracts and reliability matter more than speed
6
GraphQL eliminates over-fetching but requires server-side complexity analysis and batching
7
No single style is 'best'
choose based on client diversity, caching needs, and contract strictness
8
Security must be handled at the field/resource level regardless of style
9
Always test the failure paths
complexity attacks, schema leaks, and versioning breaks

Common mistakes to avoid

6 patterns
×

Memorising syntax before understanding the concept

Symptom
Can write a REST endpoint but doesn't know why it should be stateless
Fix
Study the architectural constraints of each style — understand the 'why' before the 'how'
×

Skipping practice and only reading theory

Symptom
Can explain differences verbally but cannot implement a working API in any style
Fix
Build a small project in each style — even a todo app. Practice exposes production surprises.
×

Choosing GraphQL for a simple CRUD app with one client

Symptom
Unnecessary complexity: resolvers, batching, cost analysis — for a basic data layer
Fix
Use REST for straightforward CRUD and reserve GraphQL for when clients have diverse data needs
×

Using SOAP for public mobile APIs

Symptom
Large XML payloads slow down mobile app; onboarding third-party developers is painful
Fix
Expose REST or GraphQL to mobile clients. Keep SOAP only for internal B2B if required.
×

Exposing GraphQL introspection in production

Symptom
Attackers can map your entire schema and identify sensitive fields like internal IDs, email patterns, or role fields.
Fix
Disable introspection in production. Use a schema registry for internal documentation. Only allow introspection in development or staging.
×

Assuming REST caching works out of the box without headers

Symptom
Clients see stale data because no Cache-Control or ETag headers are set. CDN returns old responses.
Fix
Explicitly set Cache-Control headers on GET responses. Use ETag for conditional requests. Test caching behaviour in a staging environment.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01SENIOR
What are the constraints of REST, and when would you violate them in pro...
Q02SENIOR
How would you handle versioning in a REST API to avoid breaking existing...
Q03SENIOR
Explain how GraphQL's N+1 problem occurs and how DataLoader solves it.
Q04SENIOR
When would you choose SOAP over REST or GraphQL for a new greenfield pro...
Q05SENIOR
What is the biggest security risk of GraphQL in production?
Q06SENIOR
How do you test a REST API that uses caching to ensure responses are cor...
Q01 of 06SENIOR

What are the constraints of REST, and when would you violate them in production?

ANSWER
REST's six constraints are: client-server, stateless, cacheable, uniform interface, layered system, and optionally code on demand. In production, statelessness is often violated by storing session state in the server (e.g., JSESSIONID). Uniform interface is dropped when using different URIs for the same resource (e.g., /getUser instead of /users/{id}). Cacheable is skipped for dynamic responses. The key trade-off: violating constraints buys you simpler code but breaks REST's scalability guarantees.
FAQ · 7 QUESTIONS

Frequently Asked Questions

01
What is REST vs SOAP vs GraphQL in simple terms?
02
Can I mix REST and GraphQL in the same system?
03
When should I avoid GraphQL?
04
Is SOAP still used in 2026?
05
What's the easiest way to migrate from REST to GraphQL?
06
How do I handle authentication in GraphQL?
07
What tools can I use to enforce API standards across teams?
🔥

That's Computer Networks. Mark it forged?

10 min read · try the examples if you haven't

Previous
WebSockets Explained
11 / 22 · Computer Networks
Next
Network Security Basics