GraphQL

GraphQL is a query-based API approach where the client specifies exactly what data it needs and in what structure. The API provides a single endpoint and a strong type system (schema) that describes the entire data graph of the application. GraphQL was originally developed at Facebook as a response to the limitations of REST APIs.

✅ When is it appropriate

GraphQL is suitable if most of the following apply:

  • different clients such as a web app and a mobile app need different subsets of the same data
  • the frontend team frequently needs new data combinations that would require adding or changing REST endpoints
  • multiple related resources currently require several separate API calls that could be combined into one
  • the data model is hierarchical or graph-like, such as users with posts with comments with authors
  • the application has a web client and a mobile client that need different response shapes from the same backend

With GraphQL, the frontend developer writes a query specifying exactly which fields to return. The server reads the query, fetches only those fields, and returns them. This removes the need for the backend team to create a separate endpoint every time the frontend needs a different combination of data.

❌ When is it NOT appropriate

GraphQL may not be ideal if:

  • the API has a fixed set of resources and never needs different field combinations
  • standard HTTP caching by URL is needed, since all GraphQL requests use the same endpoint and POST method, which bypasses browser and CDN caches
  • the API will be publicly available to any developer, since GraphQL allows querying any combination of fields and can expose unintended data if access control is not configured carefully
  • the team has no prior GraphQL experience and the project has a short deadline
  • the backend infrastructure needs to stay as simple as possible

GraphQL uses a single endpoint for all requests. Standard browser and CDN caching works by storing responses per URL, so it does not work automatically with GraphQL. Setting up caching requires additional tooling on the client or server.

👍 Advantages

  • the client requests exactly the fields it needs, so the server never returns unused data and never omits required data
  • fetching a user with their posts and comments in one request rather than three separate REST calls
  • the schema defines every type and field in one place, so clients know exactly what the API can return without reading documentation
  • single endpoint for the entire API
  • new fields can be added to the schema without removing old ones, so older clients keep working without a versioned API migration

👎 Disadvantages

  • requires a schema, resolver functions, and a GraphQL server layer that does not exist in a plain REST setup
  • responses are not cacheable by URL, so per-query caching must be configured separately
  • a client can request deeply nested data in a single query, which may trigger thousands of database calls if the server does not batch them
  • without depth and complexity limits, a malicious or poorly written query can overload the server
  • all queries arrive at the same endpoint with the same HTTP method, so server logs show less context than REST logs where each endpoint has a descriptive URL

🛠️ Typical use cases

  • frontend-heavy applications
  • complex web and mobile applications
  • BFF (Backend for Frontend) layers
  • internal APIs for multiple clients
  • products with rapidly changing requirements

⚠️ Common mistakes (anti-patterns)

  • not setting a maximum query depth, allowing a client to request users with posts with comments with authors with posts indefinitely, producing a query that joins every table repeatedly
  • not setting a query complexity limit, so a single request can trigger hundreds of database calls and slow the server to a halt
  • fetching the same related record once per parent item instead of batching all related records in one database call, known as the N+1 problem
  • designing the schema as a direct mirror of the database tables rather than around the data the clients actually need
  • exposing the GraphQL endpoint publicly without authentication, allowing anyone to introspect the full schema and query any data

The most common performance failure is the N+1 problem. If a query returns ten posts and each post fetches its author separately, the server makes eleven database calls instead of one. This is hard to notice in development with small datasets and only appears at scale when the server slows down under real traffic.

💡 How to build on it wisely

Recommended approach:

  1. Design the schema around what the clients need to display rather than around how the database tables are structured.
  2. Set a maximum query depth of five to seven levels and a complexity limit to prevent expensive nested queries from reaching the database.
  3. Use a DataLoader library to batch related database lookups into a single query per request, preventing the N+1 problem where fetching ten posts triggers ten separate author lookups.
  4. Log every query in production with its execution time. Queries that consistently take over 200 milliseconds indicate a missing batch or an index on the database.
  5. Keep file uploads, webhooks, and long-running jobs as REST or WebSocket endpoints rather than forcing them through GraphQL, where they add complexity without benefit.

If every frontend change requires adding a new REST endpoint, if a mobile app receives far more data than it displays, or if multiple clients need different shapes of the same data, these are concrete signals that GraphQL will reduce friction. If the API is simple and stable with only a few endpoints, REST with standard HTTP caching is a better fit.

Feedback & Sharing

Give us your thoughts on this page, or share it with others who may find it useful.

Share with your network:

Feedback

Found this helpful? Let me know what you think or suggest improvements 👉 Contact me.