GraphQL is a query language for APIs that lets clients request exactly the data they need. Unlike REST where the server defines what data each endpoint returns, GraphQL puts the client in control. You write a query describing what you want, and the server returns exactly that - nothing more, nothing less.
GraphQL
A query language for APIs that allows clients to request exactly the data they need.
GraphQL vs REST
| Aspect | REST | GraphQL |
|---|---|---|
| Endpoints | Multiple (/users, /posts) | Single (/graphql) |
| Data fetching | Fixed response per endpoint | Client specifies fields |
| Over-fetching | Common - get all fields | None - get only requested |
| Under-fetching | Common - need multiple calls | None - get related data in one query |
| Versioning | URL or header (/v1/, /v2/) | Usually not needed |
| Caching | HTTP caching built-in | More complex, needs tools |
Core Concepts
Schema: Defines your API's types and their relationships. It's a contract between client and server.
Queries: Read operations. Ask for data, get it back.
Mutations: Write operations. Create, update, or delete data.
Subscriptions: Real-time updates. Server pushes data when something changes.
Resolvers: Functions that fetch the actual data for each field in your schema.
The Schema
The schema defines what's possible in your API:
Types: Define the shape of data
- Scalar types:
String,Int,Float,Boolean,ID - Object types: Custom types with fields
- Lists:
[User]- array of users - Non-null:
String!- can't be null
Root types:
Query: Entry point for readsMutation: Entry point for writesSubscription: Entry point for real-time
Why GraphQL?
Solve over-fetching:
REST GET /users/1 returns everything about the user. GraphQL returns only what you ask for.
Solve under-fetching:
REST: call /users/1, then /users/1/posts, then /posts/1/comments. GraphQL: one query gets user with posts and comments.
Strongly typed: Schema acts as documentation. Tools provide autocomplete and validation.
Evolve without versions: Add fields without breaking clients. Deprecate old fields gradually.
Single request: Mobile apps on slow networks benefit from fewer roundtrips.
When to Use GraphQL
Multiple clients with different needs: Mobile app needs minimal data, web dashboard needs everything. GraphQL lets each request exactly what it needs.
Complex, interconnected data: Social networks, e-commerce with products/reviews/users - GraphQL handles nested relationships elegantly.
Rapid frontend iteration: Frontend can request new fields without backend changes (if schema allows).
API aggregation: GraphQL can sit in front of multiple services, providing unified interface.
When NOT to Use GraphQL
Simple CRUD APIs: If you just fetch and update single resources, REST is simpler.
File uploads: GraphQL handles this poorly. Use REST for files.
Heavy caching needs: HTTP caching is harder with GraphQL since all requests go to one endpoint.
Real-time only: If you only need server push without queries, SSE or WebSocket might be simpler.
N+1 Problem in GraphQL
GraphQL is particularly vulnerable to N+1:
Query asks for 100 users with their posts. Without optimization:
- 1 query for users
- 100 queries for posts (one per user)
Solution: DataLoader Batches requests within a single tick. Collects all user IDs, makes one query for all posts.
Common Mistakes
1. Not using DataLoader: Every nested field causes N+1 queries. DataLoader is mandatory, not optional.
2. Exposing database schema directly: GraphQL schema should match client needs, not database tables.
3. No depth limiting:
Malicious query: user { friends { friends { friends { ... } } } } can crash your server. Limit query depth.
4. Ignoring errors:
GraphQL returns 200 even with errors. Check the errors array, not just status code.
5. Not documenting: Just because schema is self-documenting doesn't mean it's well-documented. Add descriptions.
Best Practices
Design schema for clients: Don't mirror your database. Design for how clients use data.
Use fragments for reusability: Define common field sets once, reuse in queries.
Implement pagination: Never return unbounded lists. Use cursor-based pagination.
Add query complexity limits: Prevent expensive queries from overwhelming your server.
Use persisted queries in production: Client sends hash instead of full query. Smaller payloads, prevents arbitrary queries.
Code Examples
GraphQL Schema and Queries
# Schema Definition
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
createdAt: DateTime!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
comments: [Comment!]!
}
type Query {
user(id: ID!): User
users(limit: Int, offset: Int): [User!]!
post(id: ID!): Post
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
}
# Query Example - Get exactly what you need
query GetUserWithPosts {
user(id: "123") {
name
email
posts {
title
comments {
text
author { name }
}
}
}
}
# Mutation Example
mutation CreatePost {
createPost(input: {
title: "GraphQL Guide"
content: "Learn GraphQL..."
authorId: "123"
}) {
id
title
}
}Related Terms
Query Parameters
Key-value pairs appended to URLs for filtering, sorting, and customizing API requests.
CORS
Cross-Origin Resource Sharing - A security mechanism that controls how web pages can request resources from different domains.
API Testing
The process of verifying that APIs work correctly, securely, and perform well.
Pagination
A technique for dividing large datasets into smaller, manageable chunks called pages.