GraphQL Resolvers
Expert guidance for implementing efficient, secure GraphQL resolvers.
Quick Reference
| Concern |
Solution |
Pattern |
| N+1 queries |
Dataloader |
Batch load relations |
| Authentication |
Context middleware |
Check before resolving |
| Authorization |
Field-level checks |
User can access this data |
| Validation |
Schema layer |
Input validation before resolvers |
| Error handling |
Wrapped errors |
Don't expose internal details |
| Context propagation |
Pass through all levels |
context.Context to nested resolvers |
What Do You Need?
- Dataloader - Batching relations to prevent N+1 queries
- Authorization - Checking access at field level
- Error handling - Proper GraphQL errors, no internal exposure
- Context - Propagating user, request-scoped data
- Validation - Schema-level validation approach
Specify a number or describe your resolver scenario.
Routing
| Response |
Reference to Read |
| 1, "dataloader", "n+1", "batch", "relation" |
dataloader.md |
| 2, "auth", "authorization", "access", "permission" |
authorization.md |
| 3, "error", "wrapped", "internal" |
errors.md |
| 4, "context", "user", "request" |
context.md |
| 5, "validation", "input", "schema" |
validation.md |
Critical Rules
- Always use dataloader for relations: Prevents N+1 queries
- Authorize at resolver level: Check user can access the data
- Never expose internal errors: Wrap before returning
- Propagate context through resolver chain: All nested resolvers need it
- Validate at schema layer: Use input validation, not in resolvers
- No circular dependencies: Be aware of resolver chains
Dataloader Pattern
// Bad: N+1 query pattern
func (r *queryResolver) Users(ctx context.Context) ([]*User, error) {
users, _ := r.db.Users() // 1 query
for _, user := range users {
posts, _ := r.db.PostsByUser(user.ID) // N queries!
user.Posts = posts
}
return users, nil
}
// Good: Using dataloader
func (r *queryResolver) Users(ctx context.Context) ([]*User, error) {
users, err := r.db.Users()
if err != nil {
return nil, err
}
// Batch load posts using dataloader
loaders := dataloader.For(ctx)
for _, user := range users {
user.Posts, err = loaders.PostsByUser.Load(user.ID)
if err != nil {
return nil, err
}
}
return users, nil
}
Authorization Pattern
// Good: Authorization check in resolver
func (r *queryResolver) User(ctx context.Context, id string) (*User, error) {
// Check authentication
viewer := auth.FromContext(ctx)
if viewer == nil {
return nil, fmt.Errorf("authentication required")
}
// Fetch user
user, err := r.db.FindUser(id)
if err != nil {
return nil, err
}
// Check authorization (users can view own profile, admins can view any)
if user.ID != viewer.ID && !viewer.IsAdmin {
return nil, fmt.Errorf("access denied")
}
return user, nil
}
Error Handling Pattern
// Bad: Exposing internal errors
func (r *mutationResolver) CreateUser(ctx context.Context, input CreateUserInput) (*CreateUserPayload, error) {
if err := r.db.CreateUser(input); err != nil {
return nil, fmt.Errorf("database error: %v", err) // Leaks DB details!
}
// ...
}
// Good: Wrapped errors
func (r *mutationResolver) CreateUser(ctx context.Context, input CreateUserInput) (*CreateUserPayload, error) {
if err := r.db.CreateUser(input); err != nil {
if errors.Is(err, db.ErrDuplicate) {
return &CreateUserPayload{
Errors: []UserError{{
Field: []string{"email"},
Message: "Email already exists",
}},
}, nil
}
return nil, fmt.Errorf("failed to create user")
}
// ...
}
Common Resolver Issues
| Issue |
Severity |
Impact |
Fix |
| N+1 queries |
Critical |
Database overload, slow |
Use dataloader |
| Missing authorization |
Critical |
Data exposure |
Add auth checks |
| Exposing internal errors |
High |
Information disclosure |
Wrap errors |
| Not propagating context |
High |
Breaks auth, timeout |
Pass ctx through |
| No validation |
Medium |
Bad data in DB |
Validate at schema |
| Circular resolver dependencies |
High |
Infinite loops |
Restructure schema |
Reference Index
Success Criteria
Resolvers are correct when:
- Dataloader used for all relations (no N+1 queries)
- Authorization checked before data access
- Internal errors wrapped, not exposed
- Context propagated through resolver chain
- Validation happens at schema layer
- No circular dependencies in resolver chains
- Field-level authorization for sensitive data
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: graphql-resolvers3description: GraphQL resolver patterns including dataloader for N+1 prevention, context propagation, authorization, error handling, and validation. Use when implementing GraphQL resolvers. Use when this capability is needed.4---56# GraphQL Resolvers78Expert guidance for implementing efficient, secure GraphQL resolvers.910## Quick Reference1112| Concern | Solution | Pattern |13|---------|----------|---------|14| N+1 queries | Dataloader | Batch load relations |15| Authentication | Context middleware | Check before resolving |16| Authorization | Field-level checks | User can access this data |17| Validation | Schema layer | Input validation before resolvers |18| Error handling | Wrapped errors | Don't expose internal details |19| Context propagation | Pass through all levels | context.Context to nested resolvers |2021## What Do You Need?22231. **Dataloader** - Batching relations to prevent N+1 queries242. **Authorization** - Checking access at field level253. **Error handling** - Proper GraphQL errors, no internal exposure264. **Context** - Propagating user, request-scoped data275. **Validation** - Schema-level validation approach2829Specify a number or describe your resolver scenario.3031## Routing3233| Response | Reference to Read |34|----------|-------------------|35| 1, "dataloader", "n+1", "batch", "relation" | [dataloader.md](./references/dataloader.md) |36| 2, "auth", "authorization", "access", "permission" | [authorization.md](./references/authorization.md) |37| 3, "error", "wrapped", "internal" | [errors.md](./references/errors.md) |38| 4, "context", "user", "request" | [context.md](./references/context.md) |39| 5, "validation", "input", "schema" | [validation.md](./references/validation.md) |4041## Critical Rules4243- **Always use dataloader for relations**: Prevents N+1 queries44- **Authorize at resolver level**: Check user can access the data45- **Never expose internal errors**: Wrap before returning46- **Propagate context through resolver chain**: All nested resolvers need it47- **Validate at schema layer**: Use input validation, not in resolvers48- **No circular dependencies**: Be aware of resolver chains4950## Dataloader Pattern5152```go53// Bad: N+1 query pattern54func (r *queryResolver) Users(ctx context.Context) ([]*User, error) {55 users, _ := r.db.Users() // 1 query56 for _, user := range users {57 posts, _ := r.db.PostsByUser(user.ID) // N queries!58 user.Posts = posts59 }60 return users, nil61}6263// Good: Using dataloader64func (r *queryResolver) Users(ctx context.Context) ([]*User, error) {65 users, err := r.db.Users()66 if err != nil {67 return nil, err68 }6970 // Batch load posts using dataloader71 loaders := dataloader.For(ctx)72 for _, user := range users {73 user.Posts, err = loaders.PostsByUser.Load(user.ID)74 if err != nil {75 return nil, err76 }77 }7879 return users, nil80}81```8283## Authorization Pattern8485```go86// Good: Authorization check in resolver87func (r *queryResolver) User(ctx context.Context, id string) (*User, error) {88 // Check authentication89 viewer := auth.FromContext(ctx)90 if viewer == nil {91 return nil, fmt.Errorf("authentication required")92 }9394 // Fetch user95 user, err := r.db.FindUser(id)96 if err != nil {97 return nil, err98 }99100 // Check authorization (users can view own profile, admins can view any)101 if user.ID != viewer.ID && !viewer.IsAdmin {102 return nil, fmt.Errorf("access denied")103 }104105 return user, nil106}107```108109## Error Handling Pattern110111```go112// Bad: Exposing internal errors113func (r *mutationResolver) CreateUser(ctx context.Context, input CreateUserInput) (*CreateUserPayload, error) {114 if err := r.db.CreateUser(input); err != nil {115 return nil, fmt.Errorf("database error: %v", err) // Leaks DB details!116 }117 // ...118}119120// Good: Wrapped errors121func (r *mutationResolver) CreateUser(ctx context.Context, input CreateUserInput) (*CreateUserPayload, error) {122 if err := r.db.CreateUser(input); err != nil {123 if errors.Is(err, db.ErrDuplicate) {124 return &CreateUserPayload{125 Errors: []UserError{{126 Field: []string{"email"},127 Message: "Email already exists",128 }},129 }, nil130 }131 return nil, fmt.Errorf("failed to create user")132 }133 // ...134}135```136137## Common Resolver Issues138139| Issue | Severity | Impact | Fix |140|-------|----------|--------|-----|141| N+1 queries | Critical | Database overload, slow | Use dataloader |142| Missing authorization | Critical | Data exposure | Add auth checks |143| Exposing internal errors | High | Information disclosure | Wrap errors |144| Not propagating context | High | Breaks auth, timeout | Pass ctx through |145| No validation | Medium | Bad data in DB | Validate at schema |146| Circular resolver dependencies | High | Infinite loops | Restructure schema |147148## Reference Index149150| File | Topics |151|------|--------|152| [dataloader.md](./references/dataloader.md) | Batching, caching, implementation |153| [authorization.md](./references/authorization.md) | Auth checks, role-based access |154| [errors.md](./references/errors.md) | Error wrapping, field errors |155| [context.md](./references/context.md) | Propagation, request-scoped data |156| [validation.md](./references/validation.md) | Schema validation, input types |157158## Success Criteria159160Resolvers are correct when:161- Dataloader used for all relations (no N+1 queries)162- Authorization checked before data access163- Internal errors wrapped, not exposed164- Context propagated through resolver chain165- Validation happens at schema layer166- No circular dependencies in resolver chains167- Field-level authorization for sensitive data168169---170> Converted and distributed by [TomeVault](https://tomevault.io/claim/jovermier) — claim your Tome and manage your conversions.171<!-- tomevault:4.0:skill_md:2026-04-13 -->