GoodNotes Architect Journal
๐Ÿ  Index 1. Single Server 2. Selecting DB 3. Relational & SQL 4. ACID Integrity 5. NoSQL Types 6. Scaling Guide 7. Load Balancing 8. SPOF & HA 9. API Design 10. Comm Protocols 11. TCP & UDP 12. REST Design 13. GraphQL Architecture 14. Authentication Protocols 15. JWT & OAuth 2 16. Authorization Models
System Design Chapter 13

GraphQL API Architecture & Schema Design

Why GraphQL Exists, Single Endpoint Architecture, Schema Definition (SDL), Queries vs. Mutations, & 200 OK Error Handling

โ˜… Single endpoint, precise client-driven fetching & strong typing โ€” eliminates over/under-fetching!

1 Why GraphQL Exists: The REST API Problem

GraphQL was developed internally by Facebook in 2012 (and open-sourced in 2015) to address the performance constraints, latency penalties, and rigid data contracts of traditional RESTful APIs on mobile networks.

โšก REST Multi-Roundtrip Waterfall vs. GraphQL Single Unified Query

โš ๏ธ REST API Approach

4 Roundtrips โ€ข 540ms+

Client executes sequential HTTP waterfalls to assemble a single screen:

1๏ธโƒฃ GET /api/users/123120ms (User)
2๏ธโƒฃ GET /api/users/123/posts180ms (Posts)
3๏ธโƒฃ GET /api/users/123/comments150ms (Comments)
4๏ธโƒฃ GET /api/users/123/likes90ms (Likes)

โŒ Bottlenecks: High radio latency on mobile, CPU drain, redundant payload headers, and massive over-fetching of unneeded database fields.

โœจ GraphQL Solution

1 Single Roundtrip โ€ข 140ms

Client sends 1 declarative query document requesting exact fields:

๐Ÿš€ POST /graphql1x RTT

Payload: { user(id: โ€œ123โ€) { name, posts, comments, likes } }

๐ŸŽฏ Zero Over-fetchingโšก 75% Faster Delivery๐Ÿ“ฆ Exact Payload Fit

โœ… Benefit: Backend resolves all requested entities concurrently in memory and returns a tailor-made JSON structure matching the query shape.

๐Ÿ“‰ Under-Fetching (The N+1 Roundtrip Trap)

An endpoint delivers insufficient data for a view, forcing the frontend to issue cascading sequential queries (e.g. Fetching 20 authors โž” then 20 individual API calls for each authorโ€™s profile details).

โš ๏ธ Causes UI layout shift & high mobile battery drain.

๐Ÿ“ฆ Over-Fetching (Payload Bloat)

A standard REST endpoint returns fixed enterprise-wide JSON models with 80+ fields when the mobile widget only renders a userโ€™s username and avatarUrl.

โš ๏ธ Wastes cellular data bandwidth and JSON parse CPU cycles.

2 GraphQL Architecture: Execution Pipeline & Flow

GraphQL operates as a declarative application query layer positioned between frontend clients and underlying data stores (SQL, NoSQL, microservices, 3rd party APIs):

๐Ÿ”ฎ GraphQL Core Architecture Pillars

๐ŸŒ 1. Single Unified Gateway

All operations route through one URL endpoint (typically POST /graphql) rather than dozens of resource-specific REST paths.

๐Ÿ“ 2. Declarative Fetching

Clients construct an Abstract Syntax Tree (AST) query describing the exact hierarchical JSON shape they want returned.

๐Ÿ“œ 3. Strongly-Typed SDL

Every field, input, and response is strictly typed with Schema Definition Language, acting as an infallible runtime contract.

โš™๏ธ 4. Resolver Functions

Each field in the schema maps to a Resolver function that fetches data from databases, caches, or downstream microservice RPCs.

๐Ÿ” 5. Introspection

Clients can query the GraphQL schema itself (__schema), enabling automated TypeScript type generation, autocomplete, and IDE linters.

๐Ÿ”Œ 6. Transport Agnostic

While typically transported over HTTP POST, GraphQL easily operates over WebSockets, Server-Sent Events (SSE), or gRPC streams.

๐Ÿ” Query-to-Response Structural Symmetry (What You Ask Is What You Get)
๐Ÿ“ค Client Query Document (POST /graphql)
query GetUserProfile {
  user(id: "usr_99") {
    id
    name
    posts(limit: 2) {
      title
      likesCount
    }
  }
}
๐Ÿ“ฅ Server JSON Response (HTTP 200 OK)
{
  "data": {
    "user": {
      "id": "usr_99",
      "name": "Alex Vance",
      "posts": [
        { "title": "Mastering GraphQL", "likesCount": 420 },
        { "title": "Microservices Guide", "likesCount": 185 }
      ]
    }
  }
}

3 Schema Design & The Type System (SDL)

GraphQL schemas are declared using the Schema Definition Language (SDL). The schema acts as a strict contract specifying available types, fields, relationships, and executable entry points:

๐Ÿ“œ schema.graphql โ€” Strongly-Typed SDL Contract
# 1. Custom Object Types
type User {
  id: ID!              # Non-null unique identifier
  name: String!        # Non-null string
  email: String         # Nullable string
  role: Role!           # Enum type
  posts: [Post!]!       # Non-null list containing non-null Post items
}

type Post {
  id: ID!
  title: String!
  author: User!
}

# 2. Enum Definition
enum Role {
  ADMIN
  MEMBER
  GUEST
}

# 3. Root Operations
type Query {
  user(id: ID!): User
  feed(limit: Int = 10): [Post!]!
}

type Mutation {
  createPost(input: CreatePostInput!): Post!
}

# 4. Dedicated Input Object
input CreatePostInput {
  title: String!
  authorId: ID!
}

โ— Type Modifiers (Nullability Matrix)

  • String โž” Nullable (can be โ€œhelloโ€ or null)
  • String! โž” Non-null (guaranteed value or error)
  • [String] โž” Nullable list of nullable strings
  • [String!]! โž” Non-null list containing non-null strings

๐Ÿงฑ 5 Built-in Scalar Primitives

Int (32-bit signed)Float (IEEE 754)String (UTF-8)Boolean (true/false)ID (Unique string)

Custom scalars like DateTime, JSON, or UUID can also be added via server scalar resolvers.

4 Operation Types: Queries, Mutations & Subscriptions

GraphQL partitions all client operations into three root archetypes with distinct execution semantics:

๐Ÿ” 1. Queries

Read-Only

โž” REST GET Equivalent โ€ข Idempotent

Top-level fields execute in parallel by default for maximum throughput. Never causes side-effects.

Query Example
query {
  user(id: "123") {
    name
    email
  }
}

โœ๏ธ 2. Mutations

State Modifying

โž” POST / PUT / DELETE Equivalent

Top-level fields execute serially in order to prevent race conditions during state writes, immediately returning updated fields.

Mutation Example
mutation {
  addPost(title: "New Post") {
    id
    createdAt
  }
}

โšก 3. Subscriptions

Real-Time Push

โž” WebSockets / SSE Event Stream

Establishes a persistent bidirectional connection. The server automatically pushes updates when specific server-side events trigger.

Subscription Example
subscription {
  postAdded {
    id
    title
  }
}

5 Error Handling: The 200 OK Envelope & Partial Failures

Unlike REST APIs that rely on HTTP transport status codes (404, 500), GraphQL requests almost always return HTTP 200 OK. Errors are treated as first-class domain values embedded inside the response payload envelope:

๐Ÿ’ก Why HTTP 200 OK for Execution Errors?

Because a GraphQL query can resolve multiple independent entity branches simultaneously, execution can be partially successful. For example, a userโ€™s core profile resolves successfully from Redis, but their external payment gateway lookup times out.

โž” HTTP 200 signifies the transport layer succeeded; the client inspects errors[] to gracefully handle field-level fallbacks.

๐Ÿ“ฅ Anatomy of a GraphQL 200 OK Partial Failure Envelope

HTTP/1.1 200 OK

Transport Handshake Succeeded

Content-Type: application/json

HTTP 200 OK โ€” Data + Errors Response Payload
{
  "data": {
    "user": {
      "name": "Sarah Conner",
      "creditScore": null
    }
  },
  "errors": [
    {
      "message": "Credit scoring service timeout (504)",
      "locations": [ { "line": 5, "column": 7 } ],
      "path": [ "user", "creditScore" ],
      "extensions": {
        "code": "SERVICE_UNAVAILABLE",
        "timestamp": "2026-08-20T10:14:00Z"
      }
    }
  ]
}

๐ŸŸข Non-Fatal Fields (Nullable)

If a failing field is nullable, only that field becomes null. The rest of the UI tree still renders!

๐Ÿ”ด Fatal Fields (Non-Null !)

If a failing field is marked !, error propagates up to parent object until a nullable container is reached.

6 Architectural Matrix: REST vs. GraphQL

Comprehensive side-by-side comparison across engineering and operational trade-offs:

Dimension๐ŸŒ RESTful APIs๐Ÿ”ฎ GraphQL Architecture
EndpointsMultiple resource endpoints (/users, /orders)Single gateway endpoint (POST /graphql)
Data ShapeServer-defined fixed payload schemasClient-specified precise field selection
Network EfficiencySubject to Over-fetching & Under-fetchingEliminates over/under-fetching in 1 roundtrip
Type SystemOptional documentation (OpenAPI / Swagger specs)Built-in, strongly-typed SDL runtime validation
HTTP CachingTrivial via standard HTTP headers (Cache-Control, CDNs)Complex; requires client normalized cache (Apollo InMemoryCache)
Error HandlingStandard HTTP status codes (404, 401, 500)Always 200 OK with response payload errors[] array
File UploadsNative & simple (multipart/form-data)Non-trivial (GraphQL multipart request spec or S3 pre-signed URLs)
Ideal Use CasePublic APIs, heavy CDN caching, simple microservices CRUDComplex SPAs, mobile applications, Aggregator Gateways (BFF)

7 Production Engineering: DataLoader, Security & Best Practices

Essential patterns for scaling and hardening GraphQL APIs in high-throughput production environments:

โšก 1. DataLoader (Batching & In-Memory Caching)

Solves the server-side N+1 database problem by collecting individual resolver IDs across a single tick of the event loop and dispatching a single batch SQL query:

SELECT * FROM users WHERE id IN (1, 2, 3โ€ฆ);

๐Ÿงฉ 2. Schema Modularity & Federation

Avoid monolithic megaschemas. Decompose domains using Apollo Federation or schema stitching where independent subgraphs join seamlessly under a unified gateway.

๐Ÿ“ฆ 3. Use Input Objects for Mutations

Always encapsulate mutation arguments in dedicated input types (e.g. input UpdateUserInput { โ€ฆ }) to preserve backwards compatibility when adding optional parameters.

๐Ÿ›‘ 4. Max Query Depth Limiting

Prevent malicious or accidental deeply nested circular queries (e.g. user โž” posts โž” author โž” posts โž” authorโ€ฆ) by rejecting queries exceeding a max depth (e.g., limit = 5 levels).

๐Ÿ›ก๏ธ Production Security: Query Cost Analysis & Persisted Queries

GraphQLโ€™s flexibility makes it vulnerable to Query Complexity DoS attacks where an attacker requests expensive fields with huge limits.

1. Query Cost Analysis:Assign static points to each field. Reject incoming requests if total calculation score > 1000 pts.
2. Automatic Persisted Queries (APQ):In production, disable arbitrary string queries from clients. Only allow pre-approved query hashes registered during CI/CD build.