GraphQL Schema Design: Thinking in Graphs, Not Endpoints
It's 2015, and we're finally seeing an alternative to the "RESTful" sprawl that plagues our mobile apps. GraphQL isn't just a query language; it's a shift in how we model data. Instead of hitting /api/v1/posts/123?include=comments,author, we query a single endpoint and get exactly what we need.
The Relay Connection Pattern
If you're building with Facebook's Relay framework, you can't just return a simple list of items. You need to follow the Connection Pattern for pagination. This uses "Edges" and "Nodes" to allow for cursor-based pagination.
type Query {
user(id: ID!): User
}
type User {
id: ID!
username: String
friends(first: Int, after: String): UserConnection
}
type UserConnection {
edges: [UserEdge]
pageInfo: PageInfo!
}
type UserEdge {
cursor: String!
node: User
}
type PageInfo {
hasNextPage: Boolean!
endCursor: String
}
Why Edges?
You might wonder why we need an Edge object between the Connection and the Node. The edge is the perfect place to put metadata that belongs to the relationship rather than the object itself. For example, in a social network, friendship_date belongs on the edge between two users.
Input Objects for Mutations
When it comes to changing data, don't use flat arguments. Use Input Objects. This makes your mutations more maintainable as your requirements grow.
mutation CreatePost($input: CreatePostInput!) {
createPost(input: $input) {
post {
id
title
}
errors {
message
}
}
}
input CreatePostInput {
title: String!
content: String!
clientMutationId: String # Vital for Relay!
}
The N+1 Problem
The biggest challenge in 2015 is the N+1 problem. If a user has 10 friends, a naive GraphQL implementation will call the User database 11 times. We're starting to see the emergence of DataLoader, a utility that uses the Node.js event loop to batch and cache these requests. It's an essential part of any production GraphQL stack.