GraphQL schema design including types, fields, pagination, nullability, naming conventions, and descriptions. Use when designing or modifying GraphQL schemas.
Resources
1Install
npx skillscat add jovermier/cc-stack-marketplace/graphql-schema-design Install via the SkillsCat registry.
SKILL.md
GraphQL Schema Design
Expert guidance for designing well-structured GraphQL schemas.
Quick Reference
| Concept | Best Practice | Example |
|---|---|---|
| Nullability | Default nullable, required only when necessary | email: String not email: String! |
| Pagination | Relay connections (edges/nodes) | users(first: Int, after: String): UserConnection! |
| Naming | PascalCase types, camelCase fields | type UserProfile { firstName: String } |
| Descriptions | All types, fields, arguments | """User account""" |
| Mutations | Noun + Verb pattern | createUser, deletePost |
| Deprecations | @deprecated with reason | @deprecated(reason: "Use newField") |
What Do You Need?
- Type design - Structs, interfaces, unions, enums
- Field design - Nullability, arguments, defaults
- Pagination - Relay-style connections
- Naming - Conventions for consistency
- Documentation - Descriptions, deprecations
Specify a number or describe your schema concern.
Routing
| Response | Reference to Read |
|---|---|
| 1, "type", "interface", "union", "enum" | types.md |
| 2, "field", "argument", "nullable", "default" | fields.md |
| 3, "pagination", "connection", "relay" | pagination.md |
| 4, "naming", "convention", "consistency" | naming.md |
| 5, "description", "deprecation", "document" | documentation.md |
Critical Rules
- Default to nullable: Easier to make required later than vice versa
- Use Relay pagination: Connections with edges/nodes, not lists
- Document everything: Schema is the API documentation
- Deprecate before removing: @deprecated with reason, wait for clients to migrate
- Noun mutations for state changes: createUser, deletePost, closeCard
- Avoid business logic in schema: Schema describes shape, not behavior
Schema Template
"""
A user in the system
"""
type User {
"""
The unique identifier of the user
"""
id: ID!
"""
The user's display name
"""
name: String!
"""
The user's email address (optional if not public)
"""
email: String
"""
Posts created by this user, paginated
"""
posts(
"""
Number of posts to return
"""
first: Int
"""
Cursor for pagination
"""
after: String
): PostConnection!
}
"""
Paginated connection of posts
"""
type PostConnection {
edges: [PostEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type PostEdge {
node: Post!
cursor: String!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}Common Schema Issues
| Issue | Severity | Fix |
|---|---|---|
| Unbounded lists | High | Use pagination connections |
| Missing descriptions | Medium | Add doc comments |
| Inconsistent nullability | Medium | Be intentional about ! |
| Breaking changes without deprecation | High | Use @deprecated first |
| CRUD-style mutations | Low | Use noun+verb (createUser) |
| No pagination on collections | High | Add Relay connections |
Nullability Guidelines
# Good: Nullable by default
type User {
id: ID!
name: String! # Required for user
email: String # Optional (not all users have email)
bio: String # Optional (not all users filled it out)
posts(first: Int): PostConnection! # Connection required, edges may be empty
}
# Avoid: Too many required fields
type User {
id: ID!
name: String!
email: String! # Required may block mutations
phone: String! # Required may block mutations
bio: String! # Required may block mutations
}Reference Index
| File | Topics |
|---|---|
| types.md | Objects, interfaces, unions, enums, scalars |
| fields.md | Nullability, arguments, defaults, lists |
| pagination.md | Relay connections, edges, nodes, cursors |
| naming.md | Conventions for types, fields, mutations |
| documentation.md | Descriptions, deprecations, comments |
Success Criteria
Schema is well-designed when:
- All types and fields have descriptions
- Collections use Relay pagination (not unbounded lists)
- Nullability is intentional (not default required)
- Naming follows conventions (PascalCase, camelCase)
- Deprecated fields have @deprecated with reason
- No breaking changes without deprecation period
- Schema reads as documentation for clients