REST vs SOAP vs GraphQL — Unbounded Queries Crash DB
Database connections spike to max with 'too many clients already' when GraphQL lacks depth limits.
20+ years shipping production systems from the metal up. Everything here is grounded in real deployments.
- ✓Solid grasp of fundamentals
- ✓Comfortable reading code examples
- ✓Basic production concepts
- 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
REST, SOAP, and GraphQL are three fundamentally different API paradigms for client-server data exchange, each with distinct trade-offs in contract rigidity, query flexibility, and operational overhead. REST (Representational State Transfer) dominates modern web APIs with its resource-oriented design using HTTP verbs and stateless operations, but its 'one endpoint per resource' model often forces clients to either over-fetch or make multiple round trips.
SOAP (Simple Object Access Protocol) provides the strictest contract via WSDL definitions and built-in error handling, making it the go-to for enterprise systems requiring formal agreements (e.g., banking, healthcare), but its XML envelope overhead and tight coupling to transport protocols (HTTP, SMTP) make it impractical for mobile or browser clients. GraphQL, developed by Facebook in 2015, solves the over-fetching problem by letting clients specify exact data shapes in a single query, but this flexibility introduces a critical failure mode: unbounded queries that can cascade into denial-of-service conditions by triggering expensive joins, nested resolvers, or database scans without server-side cost analysis or depth limiting.
In production, the choice between these styles often comes down to whether you prioritize developer velocity (GraphQL), operational predictability (REST), or contractual rigor (SOAP). REST remains the default for public APIs (e.g., Stripe, GitHub) because its caching semantics and tooling maturity (OpenAPI, Postman) reduce operational surprises.
SOAP survives in regulated industries where WS-Security and transactional reliability are non-negotiable—think SWIFT financial messaging or HL7 healthcare exchanges. GraphQL excels in complex, data-dense UIs (e.g., Shopify storefronts, GitHub's v4 API) but demands disciplined schema design, query cost analysis (e.g., GitHub's node limits, Shopify's complexity scoring), and resolver optimization to prevent the exact 'unbounded queries crash DB' scenario this article addresses.
The critical insight for senior engineers is that no style inherently prevents database crashes—they just shift where the risk manifests. REST's fixed endpoints make it easier to reason about load per route but harder to optimize for varied client needs.
SOAP's rigid contracts prevent ad-hoc queries but encourage monolithic operations that can still time out. GraphQL's flexibility is the most dangerous because a single query { users { posts { comments { author } } } } can trigger N+1 queries, Cartesian joins, or recursive depth explosions without explicit server-side guards.
The decision framework in this article will help you evaluate which failure modes your team can afford to manage, and the migration patterns show how to coexist styles (e.g., REST for mutations, GraphQL for reads) without inheriting the worst of both worlds.
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.
REST, SOAP, GraphQL — Three Data Fetching Contracts, One Common Failure
REST, SOAP, and GraphQL are three distinct API paradigms that define how a client requests data from a server. REST (Representational State Transfer) treats every resource as a URL endpoint, returning a fixed shape (usually JSON). SOAP (Simple Object Access Protocol) wraps requests in an XML envelope with strict schemas (WSDL). GraphQL lets the client specify exactly which fields it wants in a single query, and the server resolves them — often recursively.
REST is stateless and cacheable by design; each endpoint returns a predefined payload. SOAP enforces contract-first development with built-in error handling and security (WS-Security), but is verbose and heavy. GraphQL exposes a single endpoint and relies on a type system (schema) to validate queries. Its resolver functions can join data from multiple sources, but a deeply nested query can trigger thousands of database calls if not bounded — a classic N+1 problem on steroids.
Use REST when you have simple CRUD operations and want broad client compatibility. Use SOAP in regulated industries (finance, healthcare) that demand formal contracts and transactional guarantees. Use GraphQL when clients need flexible data shapes and you control the server — but you must implement query cost analysis, depth limiting, and pagination. The real-world cost of ignoring these limits: a single unbounded GraphQL query can collapse a PostgreSQL cluster in seconds.
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.
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); } }
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.
<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>
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.
- 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.
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 }
posts: [Post] field in the User type? Without DataLoader, fetching 100 users triggers 100 additional queries for posts. Always batch relation resolvers.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.
Use this decision tree:
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.
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; } }
- 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.
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.
# 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
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.
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)); } }
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.
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")); } }
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.
# TheCodeForge — API 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
GraphQL Schema Design: Why Your Backend Isn't a Join Monster
Every GraphQL newbie maps their database tables 1:1 into schema types. Three weeks later, they're debugging N+1 queries and blaming the framework. The schema isn't your database. It's a contract between clients and resolvers. Design for the query patterns, not the storage. Start with the UI requirements. What fields does the product detail page need? Now flatten that into a type. If a resolver calls five microservices to assemble one field, you've designed a public endpoint that'll bankrupt your ops budget. Use data loaders for batching. Cache aggressively on type resolvers. A well-designed schema reduces resolver depth, minimizes join logic, and keeps your database from becoming a bottleneck. When you model your schema, think in terms of the client's single request, not your DB's relational model.
# io.thecodeforge # Bad: mirrors DB joins # type Order { # customer: Customer # triggers 5 resolver calls # items: [Item] # another N queries # } # Good: denormalized for fetch pattern type Order { orderId: ID! customerName: String! customerEmail: String! itemCount: Int! totalAmount: Float! } type Query { getOrder(id: ID!): Order }
GraphQL Mutations: Transactions Are Your Friends, Idempotency Is Your God
REST PUT is naturally idempotent. GraphQL mutations are not. A user double-clicks 'Place Order' and your mutation fires twice. Now you have two conflicting orders and one angry customer. Mutations require explicit idempotency keys. Generate a client-supplied idempotencyKey on every mutation call. Your resolver checks if that key was already processed, then returns the cached result. Without this, retry logic becomes a nightmare. Also: think in transactions. GraphQL batches multiple mutations in one request. If mutation 2 fails, mutation 1 already changed state. Use database transactions or a saga pattern. Wrap your mutations in atomic operations. For payment flows, never assume the mutation succeeded until the read-back confirms it. Production GraphQL services log all mutation payloads, track idempotency keys, and implement circuit breakers on error rates.
// io.thecodeforge import java.util.concurrent.ConcurrentHashMap; public class OrderMutation { private final ConcurrentHashMap<String, OrderResult> idempotencyCache = new ConcurrentHashMap<>(); public OrderResult placeOrder(String idempotencyKey, OrderInput input) { // Check if already processed OrderResult cached = idempotencyCache.get(idempotencyKey); if (cached != null) return cached; // Begin DB transaction try { Order order = saveOrder(input); deductInventory(input); chargePayment(input); OrderResult result = new OrderResult(order, Status.SUCCESS); idempotencyCache.put(idempotencyKey, result); return result; } catch (Exception e) { rollbackOrder(); // Rollback the entire saga throw new MutationException("Order failed after inventory deduction", e); } } }
GraphQL Query Explodes Database Connection Pool
- 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.
curl -o /dev/null -s -w 'Total: %{time_total}s\n' https://api.example.com/userstail -n 100 /var/log/nginx/access.log | awk '{print $NF, $7}' | sort -rn | headcurl -o /dev/null -s -w 'connect: %{time_connect} starttransfer: %{time_starttransfer} total: %{time_total}\n' https://soap.example.com/serviceopenssl s_client -connect soap.example.com:443 -servername soap.example.com </dev/null 2>/dev/null | grep 'SSL handshake'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;curl -sI https://api.example.com/users | grep -i 'cache\|etag\|last-modified'grep -rn 'Cache-Control' src/main/java/io/thecodeforge/controller/| Dimension | REST | SOAP | GraphQL |
|---|---|---|---|
| Key Idea | Resources (nouns) | Actions via envelopes | Client-defined queries |
| Data Format | JSON (or XML) | XML only | JSON (query and response) |
| Caching | Built-in (HTTP cache) | None (application-level) | Difficult (query-specific) |
| Performance Overhead | Low (~30% over raw data) | High (~200% due to XML) | Varies (query complexity) |
| Contract | Informal (OpenAPI optional) | Formal (WSDL) | Schema (SDL) but no runtime contract |
| Best For | Public APIs, mobile backends | Enterprise B2B, finance | Complex UIs, data dashboards |
| Worst For | Complex data relationships | Public mobile APIs | Simple CRUD with caching |
| File | Command / Code | Purpose |
|---|---|---|
| io | @RestController | REST |
| soap-request.xml | | SOAP | |
| schema.graphql | type Query { | GraphQL |
| io | public enum ApiStyle { | Choosing an API Style |
| gateway-routes.yml | routes: | Migrating Between Styles and Coexistence Patterns |
| io | @Component | API Security |
| io | class UserResolverTest { | Testing Strategies for REST, SOAP, and GraphQL |
| benchmark.sh | echo "=== REST benchmark ===" | API Performance |
| schema.graphql | type Order { | GraphQL Schema Design |
| OrderMutation.java | public class OrderMutation { | GraphQL Mutations |
Key takeaways
Common mistakes to avoid
6 patternsMemorising syntax before understanding the concept
Skipping practice and only reading theory
Choosing GraphQL for a simple CRUD app with one client
Using SOAP for public mobile APIs
Exposing GraphQL introspection in production
Assuming REST caching works out of the box without headers
Interview Questions on This Topic
What are the constraints of REST, and when would you violate them in production?
How would you handle versioning in a REST API to avoid breaking existing clients?
Explain how GraphQL's N+1 problem occurs and how DataLoader solves it.
When would you choose SOAP over REST or GraphQL for a new greenfield project?
What is the biggest security risk of GraphQL in production?
{ allUsers { posts { comments { author { ... } } } } } can generate exponential database load. The fix: disable introspection in production, implement query cost analysis and depth limiting, and use persisted queries for known clients.How do you test a REST API that uses caching to ensure responses are correct?
Frequently Asked Questions
REST vs SOAP vs GraphQL is a fundamental concept in CS Fundamentals. Think of it as a tool — once you understand its purpose, you'll reach for it constantly.
Yes, it's common. Use REST for simple CRUD endpoints and GraphQL for complex data composition. For example, expose /users as REST for basic operations, and have a GraphQL endpoint for dashboards. Ensure clear boundaries and avoid creating two data sources that can produce inconsistent results.
Avoid GraphQL when: (1) your clients have uniform data needs (e.g., a mobile app that always shows the same screen), (2) you need aggressive caching at the HTTP level, or (3) your team lacks the operational maturity to manage resolver performance and complexity limits. GraphQL adds a lot of boilerplate for simple CRUD.
Yes, extensively in banking, insurance, and government. Many legacy systems expose SOAP APIs, and new integrations between regulated entities often require SOAP for audit trails and formal contracts. But greenfield projects should avoid it unless compliance mandates it.
Use the strangler fig pattern: add a GraphQL endpoint alongside your REST API, and migrate clients one by one. Use a gateway to route requests based on client type or version. Keep the old endpoints running until all consumers have moved. Don't attempt a big-bang rewrite — it's too risky.
Authentication in GraphQL is typically done at the HTTP level (Bearer token in Authorization header) and then passed to resolvers via context. Authorization must be checked per field — resolvers should validate that the authenticated user has permission to access the requested data. Never trust the client to self-limit its own access.
For REST: Spectral or Redocly CLI for OpenAPI linting. For SOAP: SoapUI or WSDL validator. For GraphQL: GraphQL Inspector, GraphQL-ESLint, and schema diffing tools. Integrate them into CI to catch breaking changes before they reach production. Also consider an API gateway with a policy engine (e.g., Kong, AWS API Gateway) to enforce rate limiting and authentication uniformly.
20+ years shipping production systems from the metal up. Everything here is grounded in real deployments.
That's Computer Networks. Mark it forged?
9 min read · try the examples if you haven't