GraphQL Cheat Sheet
GraphQL exposes a typed schema over HTTP: clients request exactly the fields they need; resolvers fetch data for each field on the server.
How to use this GraphQL cheat sheet Schema Definition Language (SDL) declares types, queries, mutations, and subscriptions. Resolvers map fields to data sources—databases, REST APIs, or in-memory stores. Apollo Server and GraphQL Yoga wrap Node.js HTTP handlers with parsing, validation, and execution.
N+1 queries happen when list resolvers trigger per-item fetches; DataLoader batches and caches requests within a single operation. Auth context attaches user identity to each request for field-level authorization.
Quick GraphQL example # schema.graphql
type Query {
user(id: ID!): User
posts: [Post!]!
}
type User { id: ID! name: String! email: String! }
type Post { id: ID! title: String! author: User! }
type Mutation {
createPost(title: String!): Post!
} Schema SDL Basics Construct Example Notes Scalar String, Int, Float, Boolean, ID Built-in scalars; custom scalars for Date, JSON Object type type User { id: ID! name: String! } Fields map to resolvers Non-null String! Required field; client must request or server must return List [Post!]! Non-null list of non-null posts Input type input CreateUserInput { name: String! email: String! } Mutation arguments as objects Enum enum Role { ADMIN MEMBER } Fixed set of values Interface interface Node { id: ID! } Shared fields implemented by types Union union SearchResult = User | Post One of several types
Queries & Mutations Operation Example Notes Query query { user(id: "1") { name email } } Read-only; parallel field resolution Named query query GetUser($id: ID!) { user(id: $id) { name } } Variables passed separately as JSON Mutation mutation { createPost(title: "Hi") { id title } } Serial execution; use for writes Variables { "id": "42" } Avoid string interpolation in client queries Fragments fragment UserFields on User { id name } Reuse field selections Aliases admin: user(id: "1") { name } Same field, different arguments Directives @include(if: $showEmail) Conditional fields at runtime Introspection __schema { types { name } } Disable in production for security
Resolvers & Server Setup Concept Example Notes Resolver signature (parent, args, context, info) => value Return field value or Promise Root Query Query: { user: (_, { id }, ctx) => ctx.db.user.find(id) } Top-level resolvers on Query/Mutation Field resolver Post: { author: (post, _, ctx) => ctx.loaders.user.load(post.authorId) } Resolve nested fields Apollo Server new ApolloServer({ typeDefs, resolvers }) Express middleware via expressMiddleware GraphQL Yoga createYoga({ schema, context }) } Modern server with built-in GraphiQL Context context: ({ req }) => ({ user: req.user, db }) Per-request auth and DB connections Error handling throw new GraphQLError("Forbidden", { extensions: { code: "FORBIDDEN" } }) Structured errors in response Subscriptions Subscription: { messageAdded: { subscribe: () => pubsub.asyncIterator(["ADDED"]) } } WebSocket transport required
DataLoader & Auth Pattern Example Purpose DataLoader create new DataLoader(ids => batchLoadUsers(ids)) Batch N+1 fetches into one query Per-request loader context.loaders.user New loader instance each request (cache scope) Batch function SELECT * FROM users WHERE id IN (...) Return results in same order as keys JWT in context const token = req.headers.authorization?.split(" ")[1] Verify token before resolvers run Field auth if (!ctx.user) throw new GraphQLError("Unauthenticated") Guard sensitive fields Role check if (ctx.user.role !== "ADMIN") throw ... Authorization after authentication Persisted queries APQ hash + GET for CDN caching Reduce payload size for mobile clients Depth/complexity limits validationRules: [depthLimit(10)] Prevent expensive malicious queries
Apollo Server with Express import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
const server = new ApolloServer({ typeDefs, resolvers });
await server.start();
app.use('/graphql', express.json(), expressMiddleware(server, {
context: async ({ req }) => ({ user: await getUser(req), loaders: makeLoaders() }),
})); Create fresh DataLoader instances inside context for each request.
DataLoader batch function const userLoader = new DataLoader(async (ids) => {
const users = await db.users.findMany({ where: { id: { in: ids } } });
const map = new Map(users.map((u) => [u.id, u]));
return ids.map((id) => map.get(id) ?? new Error(`No user ${id}`));
}); Return array aligned with input keys; errors per key are supported.
Client query with variables const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) { id name posts { title } }
}
`;
client.query({ query: GET_USER, variables: { id: '1' } }); Never embed user input directly into query strings—always use variables.
Common mistakes Returning deeply nested queries without DataLoader, causing N+1 database hits. Leaving introspection enabled in production, exposing full schema to attackers. Using mutations for reads or relying on GET with full query strings in logs. Omitting query depth/complexity limits, allowing DoS via expensive nested queries. Key takeaways SDL defines the contract; resolvers implement data fetching per field. Batch with DataLoader inside per-request context to eliminate N+1. Pass auth via context and enforce at field or mutation level. Use variables, fragments, and persisted queries for safe, efficient clients.
Pro Tip
Log operationName and variables server-side—not the full query string—to debug without leaking PII in access logs.
Common GraphQL Mistakes Go to next item