GraphQL is an open-source data query and manipulation language for APIs, and a runtime for fulfilling queries with existing data. It was developed internally by Facebook in 2012 before being publicly released in 2015, and is now hosted by the GraphQL Foundation.
By providing a complete and understandable description of the data in your API, GraphQL gives clients the power to ask for exactly what they need and nothing more. This mitigates common REST issues like over-fetching (retrieving unnecessary fields) and under-fetching (needing to make multiple round-trips to nested endpoints).
Architecture & Resolver Engine
In a GraphQL architecture, client applications send query payloads detailing the exact fields they require. The GraphQL execution engine processes this request against a strongly typed Schema, resolving fields through dedicated execution handlers.
The GraphQL Execution Pipeline
When a client requests data from a GraphQL API, the engine processes the operation through a series of structured steps:
- Request POST: The client sends a single HTTP
POSTrequest to the/graphqlendpoint containing the query string (selection set) and variable parameters. - Parsing & Validation: The GraphQL Engine parses the query and validates it against the strongly typed Schema to check for syntax errors, unknown fields, or security depth violations.
- Resolver Invocation: The execution engine invokes dedicated Field Resolvers for each requested node in the query selection tree.
- Data Fetching: Resolvers run concurrently to fetch information from underlying systems—performing SQL database reads, hitting key-value caches like Redis, or routing requests to external REST microservices.
- Payload Serialization: The engine packages the resolved values into a single JSON object matching the exact shape of the client’s original query, returning it to the application.
Schema Definition Language (SDL)
At the heart of any GraphQL service is a schema, which defines the types, fields, and operations available. The schema is written using the standard Schema Definition Language (SDL).
Core Types
# Object Type representing a catalog resourcetype User { id: ID! name: String! email: String! role: UserRole! posts(limit: Int): [Post!]!}
# Enum Type restricting role valuesenum UserRole { ADMIN EDITOR MEMBER}
# Nested Object Typetype Post { id: ID! title: String! content: String author: User!}Operation Root Types
Every schema defines root operation types: Query (read), Mutation (write), and Subscription (real-time stream).
type Query { me: User userById(id: ID!): User searchPosts(query: String!): [Post!]!}
type Mutation { createPost(title: String!, content: String): Post! deletePost(id: ID!): Boolean!}Operation Types
Clients execute operations by sending payloads specifying the operation type, name, variables, and selection set.
1. Queries (Read)
Unlike REST endpoints where the payload structure is determined by the server, GraphQL queries let the client select specific properties.
# Requestquery GetMyProfile { me { name role posts(limit: 2) { title } }}// Response{ "data": { "me": { "name": "Steve McDougall", "role": "ADMIN", "posts": [ { "title": "Understanding HTTP Caching" }, { "title": "Getting Started with OpenAPI 3.1" } ] } }}2. Mutations (Write)
Mutations are used to create, update, or delete data on the server. Like queries, mutations return resolved objects, allowing immediate client UI updates.
# Requestmutation CreateNewArticle($title: String!, $body: String!) { createPost(title: $title, content: $body) { id title author { name } }}Comparison: OpenAPI (REST) vs. GraphQL
| Feature | OpenAPI / REST | GraphQL |
|---|---|---|
| Data Fetching | Multi-endpoint; server defines response shape. | Single endpoint (/graphql); client defines shape. |
| Typing & Validation | JSON Schema (external validation). | Strongly typed Schema (built-in parser validation). |
| Versioning | URI paths (/v1/), header versioning, or media type. |
Versionless; deprecated fields are marked (@deprecated). |
| Real-time | Webhooks, WebSockets, or Server-Sent Events (SSE). | Native Subscriptions via WebSockets or SSE. |
| Caching | Native HTTP Caching (ETag, Cache-Control). | Client-side/Application layer caching (Apollo/Relay). |
Best Practices in GraphQL Design
- Use HTTP POST: All operations (including queries) should be sent as HTTP
POSTrequests with aContent-Type: application/jsonheader, containing the query string and variable objects. - Handle Errors with Partial Data: GraphQL returns a HTTP
200 OKstatus even if fields fail to resolve. Errors are returned inside anerrorsarray in the JSON response, alongside a partialdataobject. - Prevent Resolver N+1 Inefficiency: Use batching utilities like DataLoader to collapse nested database queries into bulk reads, preventing execution query overload.
- Enforce Query Depth Limits: Restrict maximum query nested depth and complexity analysis to protect the GraphQL engine against denial-of-service recursion attacks.