Working with GraphQL APIs
1. Defining GraphQL Schemas
| Type | Purpose |
|---|---|
| type | Object type with fields |
| input | Input object for mutations |
| enum | Fixed value set |
| interface | Abstract type for shared fields |
| union | One of several types |
| scalar | Custom primitive (e.g. DateTime) |
| directive | Annotation (@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
| Element | Detail |
|---|---|
| Operation | query keyword (default) |
| Arguments | Per-field; typed |
| Variables | $id: ID! separate from query text |
| Aliases | a: order(id:1) b: order(id:2) |
| Directives | @include(if:$x), @skip(if:$x) |
3. Implementing Mutations
| Concern | Best Practice |
|---|---|
| Naming | verbNoun: createOrder, cancelOrder |
| Input Object | Single input arg for evolvability |
| Payload Object | Return wrapper with errors + data |
| Errors | Errors-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
| Transport | Notes |
|---|---|
| graphql-ws | Modern WebSocket protocol (replaces subscriptions-transport-ws) |
| SSE | HTTP/1.1 server-sent events; simpler infra |
| Backed by | Redis Pub/Sub, Kafka, NATS |
| Auth | Token via connection_init payload |
5. Implementing Resolvers
| Argument | Description |
|---|---|
| parent / source | Parent object's resolved value |
| args | Field arguments |
| context | Per-request data (auth, dataloaders) |
| info | AST, 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
| Solution | How |
|---|---|
| DataLoader | Per-request batch + cache by key |
| Look-ahead Resolver | Inspect info AST to prefetch joins |
| Persisted Joins | Materialize via SQL JOIN at root resolver |
| @defer/@stream | Stream 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 Type | Use |
|---|---|
| Named Fragment | Reuse field selections across queries |
| Inline Fragment | Type-conditional selection on unions/interfaces |
| Co-located | Define 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
| Style | Schema Shape |
|---|---|
| Offset | orders(offset:Int, limit:Int) |
| Relay Connection | edges{node,cursor}, pageInfo{hasNextPage,endCursor} |
| Forward + Backward | first/after, last/before |
9. Implementing Authentication
| Strategy | Where |
|---|---|
| JWT in HTTP header | Validated in middleware → context |
| Field-level auth | Schema directive @auth(role: ADMIN) |
| Resolver guard | Throw AuthenticationError early |
| Subscriptions | Auth in connection_init; re-check per event |
10. Handling Errors
| Approach | Detail |
|---|---|
| Top-level errors | GraphQL spec: errors[] with path, extensions |
| Errors as data | Typed union: OrderResult = Order | NotFound | Forbidden |
| Error codes | extensions.code = "BAD_USER_INPUT" |
| Masking | Hide internals in prod (formatError) |
11. Implementing Federation
| Concept | Description |
|---|---|
| Subgraph | Service owning subset of types |
| Supergraph | Composed schema (router) |
| @key | Defines entity primary key for joins |
| @external / @requires / @provides | Cross-service field deps |
| Router | Apollo 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
| Technique | Benefit |
|---|---|
| Persisted Queries (APQ) | Smaller payload, allow-list |
| Query Complexity Limits | Reject expensive queries |
| Query Depth Limits | Prevent deeply nested attacks |
| Response Cache | Per-field TTL via @cacheControl |
| DataLoader | Batching, deduping |
| Compiled Queries | Pre-parse hot ops |