Working with GraphQL APIs

1. Defining GraphQL Schemas

TypePurpose
typeObject type with fields
inputInput object for mutations
enumFixed value set
interfaceAbstract type for shared fields
unionOne of several types
scalarCustom primitive (e.g. DateTime)
directiveAnnotation (@deprecated, @auth)

Example: SDL

scalar DateTime

type Order {
  id: ID!
  status: OrderStatus!
  total: Float!
  createdAt: DateTime!
  items: [OrderItem!]!
}

enum OrderStatus { DRAFT PAID SHIPPED CANCELLED }

input CreateOrderInput { customerId: ID!, items: [OrderItemInput!]! }

type Query    { order(id: ID!): Order, orders(status: OrderStatus): [Order!]! }
type Mutation { createOrder(input: CreateOrderInput!): Order! }
type Subscription { orderStatusChanged(id: ID!): Order! }

2. Implementing Queries

ElementDetail
Operationquery keyword (default)
ArgumentsPer-field; typed
Variables$id: ID! separate from query text
Aliasesa: order(id:1) b: order(id:2)
Directives@include(if:$x), @skip(if:$x)

3. Implementing Mutations

ConcernBest Practice
NamingverbNoun: createOrder, cancelOrder
Input ObjectSingle input arg for evolvability
Payload ObjectReturn wrapper with errors + data
ErrorsErrors-as-data (typed) over top-level errors

Example: Mutation with payload

type CreateOrderPayload {
  order: Order
  userErrors: [UserError!]!
}
type UserError { field: [String!]!, message: String!, code: String! }
extend type Mutation { createOrder(input: CreateOrderInput!): CreateOrderPayload! }

4. Using Subscriptions

TransportNotes
graphql-wsModern WebSocket protocol (replaces subscriptions-transport-ws)
SSEHTTP/1.1 server-sent events; simpler infra
Backed byRedis Pub/Sub, Kafka, NATS
AuthToken via connection_init payload

5. Implementing Resolvers

ArgumentDescription
parent / sourceParent object's resolved value
argsField arguments
contextPer-request data (auth, dataloaders)
infoAST, field path, schema info

Example: Resolver (Apollo Server 4)

const resolvers = {
  Query: {
    order: (_, { id }, { loaders }) => loaders.order.load(id)
  },
  Order: {
    items: (order, _, { loaders }) => loaders.itemsByOrder.load(order.id)
  }
};

6. Handling N+1 Query Problems

SolutionHow
DataLoaderPer-request batch + cache by key
Look-ahead ResolverInspect info AST to prefetch joins
Persisted JoinsMaterialize via SQL JOIN at root resolver
@defer/@streamStream large lists progressively

Example: DataLoader

import DataLoader from "dataloader";
const itemsByOrder = new DataLoader(async (orderIds) => {
  const rows = await db.items.findMany({ where: { orderId: { in: orderIds } } });
  return orderIds.map(id => rows.filter(r => r.orderId === id));
});

7. Using Fragments

Fragment TypeUse
Named FragmentReuse field selections across queries
Inline FragmentType-conditional selection on unions/interfaces
Co-locatedDefine near component (Relay/Apollo)

Example: Fragment

fragment OrderSummary on Order { id status total createdAt }
query { order(id:"1") { ...OrderSummary items { sku qty } } }

8. Implementing Pagination

StyleSchema Shape
Offsetorders(offset:Int, limit:Int)
Relay Connectionedges{node,cursor}, pageInfo{hasNextPage,endCursor}
Forward + Backwardfirst/after, last/before

9. Implementing Authentication

StrategyWhere
JWT in HTTP headerValidated in middleware → context
Field-level authSchema directive @auth(role: ADMIN)
Resolver guardThrow AuthenticationError early
SubscriptionsAuth in connection_init; re-check per event

10. Handling Errors

ApproachDetail
Top-level errorsGraphQL spec: errors[] with path, extensions
Errors as dataTyped union: OrderResult = Order | NotFound | Forbidden
Error codesextensions.code = "BAD_USER_INPUT"
MaskingHide internals in prod (formatError)

11. Implementing Federation

ConceptDescription
SubgraphService owning subset of types
SupergraphComposed schema (router)
@keyDefines entity primary key for joins
@external / @requires / @providesCross-service field deps
RouterApollo Router, Cosmo, Mesh

Example: Federated entity

# users subgraph
type User @key(fields: "id") { id: ID!, email: String! }

# orders subgraph
extend type User @key(fields: "id") { id: ID! @external, orders: [Order!]! }

12. Optimizing Query Performance

TechniqueBenefit
Persisted Queries (APQ)Smaller payload, allow-list
Query Complexity LimitsReject expensive queries
Query Depth LimitsPrevent deeply nested attacks
Response CachePer-field TTL via @cacheControl
DataLoaderBatching, deduping
Compiled QueriesPre-parse hot ops