Skip to content

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.