graphql-patterns
GraphQL schema design, resolver patterns, N+1 prevention with DataLoader, and federation. Use when designing GraphQL APIs, implementing resolvers, or optimizing GraphQL performance.
Works with
---
name: graphql-patterns
description: GraphQL schema design, resolver patterns, N+1 prevention with DataLoader, and federation. Use when designing GraphQL APIs, implementing resolvers, or optimizing GraphQL performance.
license: MIT
---
# GraphQL Patterns
Design, implement, and optimize GraphQL APIs — schema design, resolver architecture,
performance tuning, federation, and schema evolution.
## When to Use
- Designing a GraphQL schema (types, queries, mutations, subscriptions).
- Implementing resolvers and data fetching logic.
- Diagnosing or preventing N+1 query problems.
- Adding pagination to list fields.
- Setting up federation across microservices.
- Evolving a schema without breaking clients.
## Instructions
### Schema Design
Design from the client perspective, not the database schema:
- **Types**: domain entities as nouns (`User`, `Order`).
- **Queries**: read operations — `user(id: ID!)`, `orders(filter: OrderFilter)`.
- **Mutations**: write operations — verb-object naming: `createOrder`, `cancelOrder`.
- **Subscriptions**: real-time streams — `orderStatusChanged(orderId: ID!)`.
Prefer non-nullable fields (`String!`) by default. Use custom scalars for domain values
(`DateTime`, `Email`). Define `input` types for mutation arguments.
### Resolver Patterns
- **Root resolvers** handle top-level Query/Mutation fields.
- **Field resolvers** handle nested fields from a different data source than the parent.
- **Default resolvers** return `parent[fieldName]` — do not write trivial resolvers.
Keep resolvers thin: extract business logic into service/domain layers.
### N+1 Problem and DataLoader
A list query triggering one query per item is the N+1 problem. **DataLoader** batches
and caches within a single request:
```javascript
const orderLoader = new DataLoader(async (userIds) => {
const orders = await db.orders.findByUserIds(userIds);
return userIds.map(id => orders.filter(o => o.userId === id));
});
// In User.orders resolver: (user) => orderLoader.load(user.id)
```
- Create a new DataLoader instance **per request** (prevents cross-request cache leaks).
- Batch function must return results in the **same order** as input keys.
### Pagination (Relay Connection Spec)
Use cursor-based pagination for list fields:
```graphql
type OrderConnection {
edges: [OrderEdge!]!
pageInfo: PageInfo!
}
type OrderEdge { cursor: String!; node: Order! }
type PageInfo { hasNextPage: Boolean!; hasPreviousPage: Boolean!; endCursor: String }
```
Cursors should be opaque (base64-encoded). Default `first` to 20, enforce max 100.
### Error Handling
**Domain errors as union types** (preferred for expected errors):
```graphql
union CreateOrderResult = Order | ValidationError | InsufficientStock
```
Clients handle errors with type-safe pattern matching instead of parsing error strings.
Use top-level `errors` array for unexpected failures (auth, server errors). Include
`extensions.code` (e.g., `UNAUTHENTICATED`) for machine-readable classification.
### Federation
For microservice architectures (Apollo Federation):
- Each service owns a subgraph with `@key` directives on shared entities.
- Gateway composes subgraphs into a supergraph at build time.
- A subgraph only extends fields it can resolve.
### Schema Evolution
- **Adding** fields/types/enum values is always safe.
- **Deprecating**: `@deprecated(reason: "Use newField")`. Monitor usage before removal.
- **Never** change a field's type or make a nullable field non-nullable.
### Persisted Queries
Use automatic persisted queries (APQ) in production: client sends a query hash, server
looks it up. Reduces bandwidth and enables query allowlisting for security.
## Examples
### Example 1: Design a schema for a task management app
```
User: Design a GraphQL schema for projects, tasks, and team members.
Agent: Designs types (Project, Task, User, Comment), queries (projects,
project(id), myTasks), mutations (createTask, assignTask, addComment),
subscription (taskUpdated). Relay-style pagination on task lists, union
result types for mutation errors, input types for all arguments.
```
### Example 2: Fix N+1 performance problem
```
User: Fetching 50 projects takes 3 seconds.
Agent: Identifies N+1: 1 query for projects + 50 for owner + 50 for
taskCount. Implements DataLoader for both fields — ownerLoader batches
user IDs, taskCountLoader batches project IDs. Response drops to 120ms.
```
### Example 3: Evolve schema to replace a deprecated field
```
User: Split User.name into firstName and lastName without breaking clients.
Agent: Adds firstName/lastName, marks name as @deprecated, resolver returns
concatenation for compatibility. Monitors usage, removes after adoption >98%.
```More API Design skills
lark-event
larksuite/cli
Lark/Feishu real-time event listening / subscribing / consuming: stream events as NDJSON via `lark-cli event consume <EventKey>` (covers IM messages/reactions/chat changes, Approval status changes, Task updates, VC meeting started/joined/ended, Minutes generated, Whiteboard updated, etc.). Use for Lark bots, real-time message processing, long-running subscribers, streaming webhook/push handlers. Supports `--max-events` / `--timeout` bounded runs and a stderr ready-marker contract — designed for AI agents running as subprocesses.
lark-contact
larksuite/cli
飞书 / Lark 通讯录:按姓名 / 邮箱解析成 open_id,或按 open_id 反查姓名 / 部门 / 邮箱 / 联系方式 / 个人状态 / 签名,以及按关键词搜索当前用户可见的机器人 / 智能体(agent)。当用户提到一个名字要下一步发消息 / 排日程,或拿到 open_id 想查具体信息时使用。不负责部门树遍历、按部门列员工、组织架构图,这类需求走原生 OpenAPI。
lark-openapi-explorer
larksuite/cli
飞书/Lark 原生 OpenAPI 探索:从官方文档库中挖掘未经 CLI 封装的原生 OpenAPI 接口。当用户的需求无法被现有 lark-* skill 或 lark-cli 已注册命令满足,需要查找并调用原生飞书 OpenAPI 时使用。

