GraphQL Concepts
Core Concepts
Schema Definition Language (SDL)
# Types
type User {
id: ID!
email: String!
name: String
posts: [Post!]!
createdAt: DateTime!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
comments: [Comment!]!
status: PostStatus!
}
# Enums
enum PostStatus {
DRAFT
PUBLISHED
ARCHIVED
}
# Input types
input CreateUserInput {
email: String!
name: String
password: String!
}
# Root types
type Query {
user(id: ID!): User
users(page: Int, size: Int): UserConnection!
post(id: ID!): Post
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
}
type Subscription {
postCreated: Post!
commentAdded(postId: ID!): Comment!
}
Type System
Scalar Types:
- ID: Unique identifier
- String: UTF-8 text
- Int: 32-bit integer
- Float: Double-precision float
- Boolean: true/false
Custom Scalars:
- DateTime: ISO 8601 timestamp
- JSON: Arbitrary JSON
- UUID: UUID string
- URL: Valid URL
Modifiers:
- !: Non-nullable
- []: List
- [!]!: Non-null list of non-null items
Queries
Basic Query
query GetUser {
user(id: "123") {
id
name
email
}
}
Nested Query
query GetUserWithPosts {
user(id: "123") {
id
name
posts {
id
title
comments {
id
content
}
}
}
}
Variables
query GetUser($userId: ID!) {
user(id: $userId) {
id
name
}
}
# Variables:
{
"userId": "123"
}
Fragments
fragment UserFields on User {
id
name
email
}
query GetUsers {
user1: user(id: "1") {
...UserFields
}
user2: user(id: "2") {
...UserFields
}
}
Mutations
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
id
email
name
}
}
# Variables:
{
"input": {
"email": "john@example.com",
"name": "John Doe",
"password": "SecurePass123"
}
}
Subscriptions
subscription OnPostCreated {
postCreated {
id
title
author {
name
}
}
}
Pagination
Cursor-based (Relay Connection Spec)
type UserConnection {
edges: [UserEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type UserEdge {
cursor: String!
node: User!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
# Query
query GetUsers($first: Int, $after: String) {
users(first: $first, after: $after) {
edges {
cursor
node {
id
name
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
Offset-based
type UserPage {
content: [User!]!
page: Int!
size: Int!
totalElements: Int!
totalPages: Int!
}
query GetUsers($page: Int!, $size: Int!) {
users(page: $page, size: $size) {
content {
id
name
}
totalPages
}
}
Error Handling
# Union approach
union CreateUserResult = User | ValidationError | ConflictError
type ValidationError {
field: String!
message: String!
}
type ConflictError {
message: String!
existingId: ID!
}
# Query
mutation CreateUser($input: CreateUserInput!) {
createUser(input: $input) {
... on User {
id
email
}
... on ValidationError {
field
message
}
... on ConflictError {
message
}
}
}
N+1 Problem
Problem:
1. Query users (1 query)
2. For each user, query posts (N queries)
Total: N+1 queries
Solutions:
1. DataLoader - Batch and cache
2. Join fetching - Eager load
3. Look-ahead - Analyze query, optimize
Security Considerations
Query depth limiting:
- Prevent deeply nested queries
- Set max depth (e.g., 10)
Query complexity:
- Assign costs to fields
- Limit total complexity
Rate limiting:
- Per query/mutation
- Per operation complexity
Field authorization:
- Per-field access control
- Role-based visibility
Best Practices
Schema Design:
✅ Use non-null (!) where appropriate
✅ Consistent naming (camelCase)
✅ Avoid generic "data" fields
✅ Use enums for fixed values
✅ Input types for mutations
Performance:
✅ Use DataLoader for batching
✅ Implement pagination
✅ Limit query depth/complexity
✅ Cache resolved data
Security:
✅ Validate input
✅ Authorize at resolver level
✅ Limit query complexity
✅ Disable introspection in production
Related Skills
graphql-spring: Spring Boot GraphQL implementation
apigen-architecture: Overall system architecture