Introduction
GraphQL provides a flexible alternative to REST, allowing clients to request exactly the data they need. This guide covers GraphQL schema design, query patterns, and best practices.
Schema Design
# Schema definition
type Query {
user(id: ID!): User
users(first: Int = 10, after: String): UserConnection!
post(id: ID!): Post
posts(author: ID, category: String, first: Int = 10): PostConnection!
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
createPost(input: CreatePostInput!): Post!
publishPost(id: ID!): Post!
}
type Subscription {
userCreated: User!
postPublished(authorId: ID): Post!
}
# Types
type User {
id: ID!
email: String!
name: String!
posts(first: Int = 10): PostConnection!
createdAt: String!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
category: String!
publishedAt: String
comments(first: Int = 10): CommentConnection!
}
type Comment {
id: ID!
content: String!
author: User!
createdAt: String!
}
# Connections for pagination
type UserConnection {
edges: [UserEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type UserEdge {
node: User!
cursor: String!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
input CreateUserInput {
email: String!
name: String!
}
input UpdateUserInput {
name: String
email: String
}
input CreatePostInput {
title: String!
content: String!
authorId: ID!
category: String!
}
Resolver Implementation
import strawberry
from typing import List, Optional
@strawberry.type
class User:
id: str
email: str
name: str
created_at: str
@strawberry.field
def posts(self, first: int = 10) -> "PostConnection":
return PostService.get_posts_by_author(self.id, first)
@strawberry.type
class Query:
@strawberry.field
def user(self, id: str) -> Optional[User]:
return UserService.get_by_id(id)
@strawberry.field
def users(self, first: int = 10, after: str = None) -> UserConnection:
return UserService.get_users(first, after)
@strawberry.type
class Mutation:
@strawberry.mutation
def create_user(self, input: CreateUserInput) -> User:
return UserService.create(input.email, input.name)
@strawberry.mutation
def delete_user(self, id: str) -> bool:
return UserService.delete(id)
# Subscription
@strawberry.type
class Subscription:
@strawberry.subscription
def user_created(self) -> User:
pubsub = get_pubsub()
for user in pubsub.subscribe("USER_CREATED"):
yield user
schema = strawberry.Schema(query=Query, mutation=Mutation, subscription=Subscription)
Query Patterns
# Fetch user with posts
query GetUserWithPosts($userId: ID!) {
user(id: $userId) {
id
name
email
posts(first: 5) {
edges {
node {
id
title
publishedAt
}
}
}
}
}
# Batch query
query Dashboard($userId: ID!) {
user(id: $userId) {
...UserFields
}
recentPosts: posts(first: 5) {
...PostFields
}
notifications(unread: true) {
id
message
}
}
fragment UserFields on User {
id
name
posts(first: 3) {
totalCount
}
}
fragment PostFields on PostConnection {
edges {
node {
id
title
}
}
}
Conclusion
GraphQL provides flexibility for clients while simplifying backend APIs. Design schemas around client needs, use connections for lists, implement proper error handling, and optimize with DataLoader for N+1 prevention.
Resources
- GraphQL.org
- Apollo Documentation
Comments