Summary
GraphQL is a query language and runtime for APIs that enables clients to request exactly the data they need. Developed by Facebook, it provides a complete description of API data through a strongly-typed schema, allowing frontend developers to fetch multiple resources in a single request. Unlike REST, GraphQL operates through a single endpoint and eliminates over-fetching and under-fetching problems. This cheatsheet covers GraphQL fundamentals, frontend integration patterns, and best practices for technical interviews.
Core Concepts
What is GraphQL?
- Query Language for APIs and runtime for executing queries
- Single Endpoint (typically
/graphql) - Strongly Typed schema
- Client specifies exact data needs
GraphQL vs REST
| GraphQL | REST |
|---|---|
| Single endpoint | Multiple endpoints |
| No over/under-fetching | Fixed data structure |
| Strongly typed | No built-in type system |
| Single request for nested data | Multiple requests (N+1 problem) |
Query Basics
Simple Query
query {
user(id: "123") {
name
email
}
}
Nested Query
query {
user(id: "123") {
name
posts {
title
comments {
text
}
}
}
}
Aliases
query {
firstUser: user(id: "1") {
name
}
secondUser: user(id: "2") {
name
}
}
Mutations
Basic Mutation
mutation {
createUser(input: {
name: "John Doe"
email: "john@example.com"
}) {
id
name
email
}
}
Update Mutation
mutation UpdateUser($id: ID!, $name: String!) {
updateUser(id: $id, name: $name) {
id
name
}
}
Delete Mutation
mutation {
deleteUser(id: "123") {
success
message
}
}
Subscriptions
Basic Subscription
subscription {
messageAdded(channelId: "123") {
id
text
user {
name
}
}
}
WebSocket Connection
// Using Apollo Client
const subscription = client.subscribe({
query: MESSAGE_ADDED_SUBSCRIPTION
}).subscribe({
next: (data) => console.log(data),
error: (err) => console.error(err),
});
Variables
Query with Variables
query GetUser($userId: ID!) {
user(id: $userId) {
name
email
}
}
Variable Usage
// In Apollo Client
const { data } = useQuery(GET_USER, {
variables: { userId: "123" }
});
Fragments
Basic Fragment
fragment UserInfo on User {
id
name
email
}
query {
user(id: "123") {
...UserInfo
}
}
Inline Fragment
query {
search(text: "GraphQL") {
... on User {
name
email
}
... on Post {
title
content
}
}
}
Directives
@include and @skip
query GetUser($showEmail: Boolean!) {
user(id: "123") {
name
email @include(if: $showEmail)
phone @skip(if: $showEmail)
}
}
Custom Directives
query {
user(id: "123") {
name
email @deprecated(reason: "Use primaryEmail")
primaryEmail
}
}
Type System
Scalar Types
Int: Signed 32-bit integerFloat: Signed double-precision floating-pointString: UTF-8 character sequenceBoolean: true or falseID: Unique identifier
Object Types
type User {
id: ID!
name: String!
email: String
posts: [Post!]!
}
Input Types
input CreateUserInput {
name: String!
email: String!
}
Enums
enum UserRole {
ADMIN
USER
GUEST
}
Interfaces
interface Node {
id: ID!
}
type User implements Node {
id: ID!
name: String!
}
Unions
union SearchResult = User | Post | Comment
Frontend Integration
Apollo Client Setup
import { ApolloClient, InMemoryCache } from '@apollo/client';
const client = new ApolloClient({
uri: 'https://api.example.com/graphql',
cache: new InMemoryCache()
});
React Hooks
useQuery
import { useQuery, gql } from '@apollo/client';
const GET_USERS = gql`
query GetUsers {
users {
id
name
}
}
`;
function Users() {
const { loading, error, data } = useQuery(GET_USERS);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return data.users.map(user => (
<div key={user.id}>{user.name}</div>
));
}
useMutation
const CREATE_USER = gql`
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
name
}
}
`;
function AddUser() {
const [createUser, { data, loading, error }] = useMutation(CREATE_USER);
const handleSubmit = () => {
createUser({
variables: {
input: { name: "John", email: "john@example.com" }
}
});
};
return <button onClick={handleSubmit}>Add User</button>;
}
useSubscription
const MESSAGE_SUBSCRIPTION = gql`
subscription OnMessageAdded($channel: String!) {
messageAdded(channel: $channel) {
id
text
}
}
`;
function Messages({ channel }) {
const { data, loading } = useSubscription(MESSAGE_SUBSCRIPTION, {
variables: { channel }
});
return <div>{data?.messageAdded?.text}</div>;
}
Error Handling
Query Error Handling
const { loading, error, data } = useQuery(GET_USER, {
errorPolicy: 'all', // 'none' | 'ignore' | 'all'
onError: (error) => {
console.error('GraphQL error:', error);
}
});
Mutation Error Handling
const [mutate] = useMutation(CREATE_USER, {
onError: (error) => {
if (error.graphQLErrors.length > 0) {
// Handle GraphQL errors
}
if (error.networkError) {
// Handle network errors
}
}
});
Performance Optimization
Query Batching
const client = new ApolloClient({
uri: '/graphql',
cache: new InMemoryCache(),
batchInterval: 10, // milliseconds
batch: true
});
Caching Strategies
const { data } = useQuery(GET_USER, {
fetchPolicy: 'cache-first', // Default
// Other options: 'cache-only', 'network-only', 'no-cache', 'cache-and-network'
});
Pagination
Offset-based
query GetPosts($offset: Int!, $limit: Int!) {
posts(offset: $offset, limit: $limit) {
id
title
}
}
Cursor-based
query GetPosts($after: String, $first: Int!) {
posts(after: $after, first: $first) {
edges {
node {
id
title
}
cursor
}
pageInfo {
hasNextPage
endCursor
}
}
}
Lazy Queries
const [getUser, { loading, data }] = useLazyQuery(GET_USER);
// Execute when needed
const handleClick = () => {
getUser({ variables: { id: "123" } });
};
Best Practices
1. Fragment Colocation
Keep fragments near components that use them
// UserProfile.js
export const USER_FRAGMENT = gql`
fragment UserProfile on User {
id
name
avatar
}
`;
2. Optimistic Updates
const [updateUser] = useMutation(UPDATE_USER, {
optimisticResponse: {
updateUser: {
__typename: 'User',
id: userId,
name: newName
}
}
});
3. Cache Updates
const [createPost] = useMutation(CREATE_POST, {
update(cache, { data: { createPost } }) {
cache.modify({
fields: {
posts(existingPosts = []) {
const newPostRef = cache.writeFragment({
data: createPost,
fragment: gql`
fragment NewPost on Post {
id
title
}
`
});
return [...existingPosts, newPostRef];
}
}
});
}
});
4. Error Boundaries
import { ErrorBoundary } from 'react-error-boundary';
function GraphQLErrorFallback({ error }) {
return <div>GraphQL Error: {error.message}</div>;
}
<ErrorBoundary FallbackComponent={GraphQLErrorFallback}>
<App />
</ErrorBoundary>
5. Avoid N+1 Queries
Use DataLoader on backend or request nested data in single query
6. Use Fragments for Reusability
Define common field sets once and reuse
7. Implement Proper Loading States
Show skeletons or spinners during data fetching
Key Interview Concepts
The N+1 Query Problem
Definition: Performance issue where fetching a list results in 1 query for the list + N queries for each item's related data
REST Example (N+1 problem):
// 1 query for users
GET /users
// N queries for each user's posts
GET /users/1/posts
GET /users/2/posts
// ... N times
GraphQL Solution (single query):
query {
users {
id
name
posts { # Fetched in same request
title
}
}
}
Schema Evolution & Versioning
GraphQL Philosophy: Evolution over versioning
| Approach | Implementation |
|---|---|
| Field Deprecation | @deprecated(reason: "Use newField") |
| Additive Changes | Add new fields without breaking existing |
| Input Type Extension | Add optional fields to inputs |
| Schema Introspection | Clients discover capabilities dynamically |
Resolver Architecture
Definition: Functions that populate data for schema fields
// Resolver structure
const resolvers = {
Query: {
user: (parent, args, context, info) => {
// parent: Previous resolver result
// args: Field arguments
// context: Shared across resolvers (auth, DB)
// info: Query AST and schema info
return context.db.getUser(args.id);
}
},
User: {
posts: (parent) => {
return getPostsByUserId(parent.id);
}
}
};
Authentication Strategies
const client = new ApolloClient({
uri: '/graphql',
headers: {
authorization: localStorage.getItem('token') || '',
}
});
Schema Composition Patterns
Schema Stitching: Combining multiple GraphQL schemas
// Legacy approach
const stitchedSchema = stitchSchemas({
schemas: [userSchema, productSchema],
mergeTypes: true
});
Federation (Modern approach):
# User service
type User @key(fields: "id") {
id: ID!
name: String
}
# Product service
type Product @key(fields: "id") {
id: ID!
price: Float
owner: User @provides(fields: "id")
}
GraphQL Execution Pipeline
1. Parse: Convert query string to AST
↓
2. Validate: Check against schema rules
↓
3. Execute: Run resolver functions
↓
4. Format: Shape response to match query
File Upload Handling
// Using Apollo Upload Client
const UPLOAD_FILE = gql`
mutation UploadFile($file: Upload!) {
uploadFile(file: $file) {
url
}
}
`;
DataLoader Pattern
Purpose: Batch and cache database queries to solve N+1 at resolver level
const userLoader = new DataLoader(async (userIds) => {
// Batch multiple user requests into single query
const users = await db.getUsersByIds(userIds);
// Return in same order as requested
return userIds.map(id => users.find(u => u.id === id));
});
// In resolver
user: (parent, { id }) => userLoader.load(id);
Real-time Update Strategies
| Strategy | Use Case | Pros | Cons |
|---|---|---|---|
| Subscriptions | Live data (chat, notifications) | True real-time, efficient | WebSocket complexity |
| Polling | Periodic updates | Simple implementation | Network overhead |
| Manual Refetch | User-triggered updates | Full control | Not real-time |
| Optimistic UI | Instant feedback | Better UX | Complex rollback |
Security Best Practices
// Query Depth Limiting
const depthLimit = require('graphql-depth-limit');
app.use('/graphql', depthLimit(5));
// Query Complexity Analysis
const costAnalysis = {
maximumCost: 1000,
scalarCost: 1,
objectCost: 2,
listFactor: 10
};
// Field-Level Authorization
const resolvers = {
User: {
email: (parent, args, context) => {
if (context.user.id !== parent.id) {
throw new ForbiddenError('Cannot access email');
}
return parent.email;
}
}
};
Quick Reference
Apollo Client Methods
useQuery()- Fetch datauseMutation()- Modify datauseSubscription()- Real-time updatesuseLazyQuery()- Fetch on demanduseApolloClient()- Direct client access
Cache Methods
cache.writeQuery()- Write entire querycache.writeFragment()- Write fragmentcache.modify()- Modify existing datacache.evict()- Remove from cachecache.gc()- Garbage collection
Fetch Policies
cache-first- Check cache first (default)network-only- Always fetch from networkcache-only- Only use cacheno-cache- Fetch and don't cachecache-and-network- Return cache, then network
Interview Preparation Checklist
- ✅ Core Concepts: Queries, mutations, subscriptions, schemas
- ✅ Type System: Scalars, objects, interfaces, unions, enums
- ✅ Frontend Tools: Apollo Client, Relay, urql basics
- ✅ Performance: N+1 problem, DataLoader, caching strategies
- ✅ Real-time: Subscriptions, WebSocket implementation
- ✅ Best Practices: Fragment colocation, optimistic updates
- ✅ Security: Query depth, complexity, authorization
- ✅ Architecture: Schema design, federation, stitching
Key Takeaway: GraphQL empowers frontend developers to request exactly the data they need in a single query, eliminating over-fetching and under-fetching while providing strong typing and excellent developer experience.