Engineering Playbook
API Design

GraphQL

The Schema, Resolvers, the N+1 Problem, and Security risks.

GraphQL

REST over-fetches (gets too much data) or under-fetches (needs too many requests). GraphQL solves this by letting the client ask for exactly what it needs.

The Schema First Approach

GraphQL is typed. You define a schema (schema.graphql) before writing code.

type User {
  id: ID!
  name: String
  posts: [Post]  # Relationship
}

type Query {
  user(id: ID!): User
}

The Resolver

A function that fetches the data for a specific field.

  • Query.user -> Fetches User from DB.
  • User.posts -> Fetches Posts where author_id = user.id.

The N+1 Problem

The most common performance killer in GraphQL.

Scenario: You query for 50 Users, and for each user, you ask for their posts.

  1. 1 Query: SELECT * FROM Users LIMIT 50
  2. 50 Queries: SELECT * FROM Posts WHERE user_id = ? (Runs once per user).

The Fix: DataLoader A batching utility.

  1. Resolvers call loader.load(id).
  2. DataLoader waits a "tick" (loop of the event loop).
  3. It collects all 50 IDs.
  4. It runs one query: SELECT * FROM Posts WHERE user_id IN (1, 2, ... 50).
  5. It distributes the results back to the resolvers.

Security Risks

GraphQL gives clients immense power. You must guard it.

1. Query Depth Limit

A malicious client can ask for: User -> Posts -> Comments -> Author -> Posts -> Comments...

  • Fix: Limit query depth to e.g., 5 levels.

2. Complexity Analysis

Some fields are harder to calculate than others.

  • Fix: Assign "points" to fields. Reject queries that cost > 1000 points.

When to use GraphQL?

  • Yes: Complex frontends with deep relational data (e.g., Facebook, Dashboard).
  • No: Simple CRUD apps, Microservice-to-Microservice comms (Use gRPC), or public APIs where you want strict caching (REST is better at HTTP caching).