graphql-expert
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseGraphQL Expert
GraphQL 专家指南
Comprehensive guide for designing and implementing GraphQL APIs.
GraphQL API设计与实现的综合指南。
GraphQL Fundamentals
GraphQL 基础概念
What is GraphQL?
什么是GraphQL?
GraphQL is a query language for APIs that:
✓ Lets clients request exactly what they need
✓ Gets multiple resources in one request
✓ Uses a type system to describe data
✓ Provides introspection (self-documenting)
GraphQL vs REST:
┌─────────────────────────────────────────┐
│ REST: Multiple endpoints, fixed shapes │
│ GET /users/1 │
│ GET /users/1/posts │
│ GET /users/1/followers │
├─────────────────────────────────────────┤
│ GraphQL: Single endpoint, flexible │
│ POST /graphql │
│ query { user(id: 1) { │
│ name │
│ posts { title } │
│ followers { name } │
│ }} │
└─────────────────────────────────────────┘GraphQL is a query language for APIs that:
✓ Lets clients request exactly what they need
✓ Gets multiple resources in one request
✓ Uses a type system to describe data
✓ Provides introspection (self-documenting)
GraphQL vs REST:
┌─────────────────────────────────────────┐
│ REST: Multiple endpoints, fixed shapes │
│ GET /users/1 │
│ GET /users/1/posts │
│ GET /users/1/followers │
├─────────────────────────────────────────┤
│ GraphQL: Single endpoint, flexible │
│ POST /graphql │
│ query { user(id: 1) { │
│ name │
│ posts { title } │
│ followers { name } │
│ }} │
└─────────────────────────────────────────┘Schema Design
Schema 设计
Type System
类型系统
graphql
undefinedgraphql
undefinedScalar Types (built-in)
Scalar Types (built-in)
String, Int, Float, Boolean, ID
String, Int, Float, Boolean, ID
Custom Scalar
Custom Scalar
scalar DateTime
scalar JSON
scalar DateTime
scalar JSON
Object Type
Object Type
type User {
id: ID!
email: String!
name: String
createdAt: DateTime!
posts: [Post!]!
}
type User {
id: ID!
email: String!
name: String
createdAt: DateTime!
posts: [Post!]!
}
Enum
Enum
enum Role {
ADMIN
USER
GUEST
}
enum Role {
ADMIN
USER
GUEST
}
Interface
Interface
interface Node {
id: ID!
}
type User implements Node {
id: ID!
... other fields
}
interface Node {
id: ID!
}
type User implements Node {
id: ID!
... other fields
}
Union
Union
union SearchResult = User | Post | Comment
union SearchResult = User | Post | Comment
Input Type (for mutations)
Input Type (for mutations)
input CreateUserInput {
email: String!
name: String!
role: Role = USER
}
undefinedinput CreateUserInput {
email: String!
name: String!
role: Role = USER
}
undefinedNullability
可空性
graphql
undefinedgraphql
undefinedField modifiers:
Field modifiers:
String # Nullable string
String! # Non-null string
[String] # Nullable list of nullable strings
[String!] # Nullable list of non-null strings
[String]! # Non-null list of nullable strings
[String!]! # Non-null list of non-null strings
String # 可空字符串
String! # 非空字符串
[String] # 可空列表,元素为可空字符串
[String!] # 可空列表,元素为非空字符串
[String]! # 非空列表,元素为可空字符串
[String!]! # 非空列表,元素为非空字符串
Best practice:
最佳实践:
- Make fields nullable by default
- 默认将字段设为可空
- Use ! only when guaranteed non-null
- 仅在确保非空时使用 !
- Lists should usually be non-null: [Item!]!
- 列表通常应设为非空: [Item!]!
undefinedundefinedSchema Structure
Schema 结构
graphql
undefinedgraphql
undefinedRoot Types
Root Types
type Query {
Read operations
user(id: ID!): User
users(limit: Int, offset: Int): [User!]!
me: User
}
type Mutation {
Write operations
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
}
type Subscription {
Real-time updates
userCreated: User!
messageReceived(roomId: ID!): Message!
}
---type Query {
读取操作
user(id: ID!): User
users(limit: Int, offset: Int): [User!]!
me: User
}
type Mutation {
写入操作
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
}
type Subscription {
实时更新
userCreated: User!
messageReceived(roomId: ID!): Message!
}
---Query Design
查询设计
Basic Queries
基础查询
graphql
undefinedgraphql
undefinedSimple query
Simple query
query GetUser {
user(id: "1") {
name
email
}
}
query GetUser {
user(id: "1") {
name
email
}
}
With variables
With variables
query GetUser($id: ID!) {
user(id: $id) {
name
email
}
}
query GetUser($id: ID!) {
user(id: $id) {
name
email
}
}
Multiple queries
Multiple queries
query Dashboard {
me {
name
notifications {
count
}
}
recentPosts(limit: 5) {
title
createdAt
}
}
undefinedquery Dashboard {
me {
name
notifications {
count
}
}
recentPosts(limit: 5) {
title
createdAt
}
}
undefinedPagination
分页
graphql
undefinedgraphql
undefinedOffset-based (simple, but has issues)
Offset-based (simple, but has issues)
type Query {
users(limit: Int!, offset: Int!): [User!]!
}
type Query {
users(limit: Int!, offset: Int!): [User!]!
}
Cursor-based (recommended)
Cursor-based (recommended)
type Query {
users(first: Int, after: String): UserConnection!
}
type UserConnection {
edges: [UserEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type UserEdge {
cursor: String!
node: User!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
type Query {
users(first: Int, after: String): UserConnection!
}
type UserConnection {
edges: [UserEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
type UserEdge {
cursor: String!
node: User!
}
type PageInfo {
hasNextPage: Boolean!
hasPreviousPage: Boolean!
startCursor: String
endCursor: String
}
Usage
Usage
query {
users(first: 10, after: "cursor123") {
edges {
cursor
node {
name
email
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
undefinedquery {
users(first: 10, after: "cursor123") {
edges {
cursor
node {
name
email
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
undefinedFiltering & Sorting
过滤与排序
graphql
input UserFilter {
name: StringFilter
email: StringFilter
role: Role
createdAt: DateFilter
}
input StringFilter {
equals: String
contains: String
startsWith: String
}
input DateFilter {
before: DateTime
after: DateTime
}
enum UserSortField {
NAME
EMAIL
CREATED_AT
}
input UserSort {
field: UserSortField!
direction: SortDirection!
}
enum SortDirection {
ASC
DESC
}
type Query {
users(
filter: UserFilter
sort: UserSort
first: Int
after: String
): UserConnection!
}graphql
input UserFilter {
name: StringFilter
email: StringFilter
role: Role
createdAt: DateFilter
}
input StringFilter {
equals: String
contains: String
startsWith: String
}
input DateFilter {
before: DateTime
after: DateTime
}
enum UserSortField {
NAME
EMAIL
CREATED_AT
}
input UserSort {
field: UserSortField!
direction: SortDirection!
}
enum SortDirection {
ASC
DESC
}
type Query {
users(
filter: UserFilter
sort: UserSort
first: Int
after: String
): UserConnection!
}Mutations
变更操作(Mutation)
Mutation Design
变更设计
graphql
undefinedgraphql
undefinedInput types for mutations
Input types for mutations
input CreatePostInput {
title: String!
content: String!
published: Boolean = false
categoryIds: [ID!]
}
input UpdatePostInput {
title: String
content: String
published: Boolean
}
input CreatePostInput {
title: String!
content: String!
published: Boolean = false
categoryIds: [ID!]
}
input UpdatePostInput {
title: String
content: String
published: Boolean
}
Mutation payloads (recommended)
Mutation payloads (recommended)
type CreatePostPayload {
post: Post
errors: [Error!]!
}
type Error {
field: String
message: String!
}
type Mutation {
createPost(input: CreatePostInput!): CreatePostPayload!
updatePost(id: ID!, input: UpdatePostInput!): UpdatePostPayload!
deletePost(id: ID!): DeletePostPayload!
}
type CreatePostPayload {
post: Post
errors: [Error!]!
}
type Error {
field: String
message: String!
}
type Mutation {
createPost(input: CreatePostInput!): CreatePostPayload!
updatePost(id: ID!, input: UpdatePostInput!): CreatePostPayload!
deletePost(id: ID!): DeletePostPayload!
}
Usage
Usage
mutation CreatePost($input: CreatePostInput!) {
createPost(input: $input) {
post {
id
title
}
errors {
field
message
}
}
}
undefinedmutation CreatePost($input: CreatePostInput!) {
createPost(input: $input) {
post {
id
title
}
errors {
field
message
}
}
}
undefinedBatch Mutations
批量变更
graphql
undefinedgraphql
undefinedFor multiple operations
For multiple operations
type Mutation {
bulkDeletePosts(ids: [ID!]!): BulkDeletePayload!
bulkUpdatePosts(updates: [PostUpdate!]!): BulkUpdatePayload!
}
input PostUpdate {
id: ID!
input: UpdatePostInput!
}
---type Mutation {
bulkDeletePosts(ids: [ID!]!): BulkDeletePayload!
bulkUpdatePosts(updates: [PostUpdate!]!): BulkUpdatePayload!
}
input PostUpdate {
id: ID!
input: UpdatePostInput!
}
---Resolvers
Resolver 实现
Basic Resolvers
基础Resolver
typescript
// TypeScript resolver implementation
const resolvers = {
Query: {
user: async (_, { id }, context) => {
return context.dataSources.users.findById(id);
},
users: async (_, { filter, sort, first, after }, context) => {
return context.dataSources.users.findMany({
filter,
sort,
first,
after,
});
},
},
Mutation: {
createUser: async (_, { input }, context) => {
// Validate
const errors = validateCreateUser(input);
if (errors.length) {
return { user: null, errors };
}
// Create
const user = await context.dataSources.users.create(input);
return { user, errors: [] };
},
},
// Field resolvers
User: {
posts: async (user, _, context) => {
return context.dataSources.posts.findByUserId(user.id);
},
fullName: (user) => {
return `${user.firstName} ${user.lastName}`;
},
},
};typescript
// TypeScript resolver implementation
const resolvers = {
Query: {
user: async (_, { id }, context) => {
return context.dataSources.users.findById(id);
},
users: async (_, { filter, sort, first, after }, context) => {
return context.dataSources.users.findMany({
filter,
sort,
first,
after,
});
},
},
Mutation: {
createUser: async (_, { input }, context) => {
// Validate
const errors = validateCreateUser(input);
if (errors.length) {
return { user: null, errors };
}
// Create
const user = await context.dataSources.users.create(input);
return { user, errors: [] };
},
},
// Field resolvers
User: {
posts: async (user, _, context) => {
return context.dataSources.posts.findByUserId(user.id);
},
fullName: (user) => {
return `${user.firstName} ${user.lastName}`;
},
},
};Resolver Context
Resolver 上下文
typescript
// Context setup
interface Context {
user: User | null;
dataSources: {
users: UserDataSource;
posts: PostDataSource;
};
loaders: {
userLoader: DataLoader<string, User>;
postLoader: DataLoader<string, Post>;
};
}
// In server setup
const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => ({
user: await getUserFromToken(req.headers.authorization),
dataSources: {
users: new UserDataSource(db),
posts: new PostDataSource(db),
},
loaders: createLoaders(),
}),
});typescript
// Context setup
interface Context {
user: User | null;
dataSources: {
users: UserDataSource;
posts: PostDataSource;
};
loaders: {
userLoader: DataLoader<string, User>;
postLoader: DataLoader<string, Post>;
};
}
// In server setup
const server = new ApolloServer({
typeDefs,
resolvers,
context: async ({ req }) => ({
user: await getUserFromToken(req.headers.authorization),
dataSources: {
users: new UserDataSource(db),
posts: new PostDataSource(db),
},
loaders: createLoaders(),
}),
});Performance Optimization
性能优化
DataLoader (N+1 Solution)
DataLoader(解决N+1问题)
typescript
import DataLoader from "dataloader";
// Create loader
const userLoader = new DataLoader<string, User>(async (ids) => {
const users = await db.users.findMany({
where: { id: { in: ids } },
});
// Return in same order as input ids
const userMap = new Map(users.map((u) => [u.id, u]));
return ids.map((id) => userMap.get(id) || null);
});
// Use in resolver
const resolvers = {
Post: {
author: (post, _, context) => {
return context.loaders.userLoader.load(post.authorId);
},
},
};typescript
import DataLoader from "dataloader";
// Create loader
const userLoader = new DataLoader<string, User>(async (ids) => {
const users = await db.users.findMany({
where: { id: { in: ids } },
});
// Return in same order as input ids
const userMap = new Map(users.map((u) => [u.id, u]));
return ids.map((id) => userMap.get(id) || null);
});
// Use in resolver
const resolvers = {
Post: {
author: (post, _, context) => {
return context.loaders.userLoader.load(post.authorId);
},
},
};Query Complexity
查询复杂度控制
typescript
import { createComplexityLimitRule } from "graphql-validation-complexity";
// Limit query complexity
const complexityLimitRule = createComplexityLimitRule(1000, {
scalarCost: 1,
objectCost: 10,
listFactor: 10,
});
// Or field-level costs
const typeDefs = gql`
type Query {
users: [User!]! @complexity(value: 10, multipliers: ["first"])
user(id: ID!): User @complexity(value: 1)
}
`;typescript
import { createComplexityLimitRule } from "graphql-validation-complexity";
// Limit query complexity
const complexityLimitRule = createComplexityLimitRule(1000, {
scalarCost: 1,
objectCost: 10,
listFactor: 10,
});
// Or field-level costs
const typeDefs = gql`
type Query {
users: [User!]! @complexity(value: 10, multipliers: ["first"])
user(id: ID!): User @complexity(value: 1)
}
`;Query Depth Limiting
查询深度限制
typescript
import depthLimit from "graphql-depth-limit";
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [depthLimit(10)],
});typescript
import depthLimit from "graphql-depth-limit";
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [depthLimit(10)],
});Persisted Queries
持久化查询
typescript
// Client sends hash instead of full query
// Reduces bandwidth, enables whitelisting
// Apollo Client setup
import { createPersistedQueryLink } from "@apollo/client/link/persisted-queries";
const link = createPersistedQueryLink({ sha256 }).concat(httpLink);
// Server validates against known queries
const server = new ApolloServer({
typeDefs,
resolvers,
persistedQueries: {
cache: new RedisCache(),
},
});typescript
// Client sends hash instead of full query
// Reduces bandwidth, enables whitelisting
// Apollo Client setup
import { createPersistedQueryLink } from "@apollo/client/link/persisted-queries";
const link = createPersistedQueryLink({ sha256 }).concat(httpLink);
// Server validates against known queries
const server = new ApolloServer({
typeDefs,
resolvers,
persistedQueries: {
cache: new RedisCache(),
},
});Authentication & Authorization
认证与授权
Context-Based Auth
基于上下文的认证
typescript
// Add user to context
const context = async ({ req }) => {
const token = req.headers.authorization?.replace("Bearer ", "");
const user = token ? await verifyToken(token) : null;
return { user };
};
// Check in resolvers
const resolvers = {
Query: {
me: (_, __, context) => {
if (!context.user) {
throw new AuthenticationError("Not authenticated");
}
return context.user;
},
},
};typescript
// Add user to context
const context = async ({ req }) => {
const token = req.headers.authorization?.replace("Bearer ", "");
const user = token ? await verifyToken(token) : null;
return { user };
};
// Check in resolvers
const resolvers = {
Query: {
me: (_, __, context) => {
if (!context.user) {
throw new AuthenticationError("未认证");
}
return context.user;
},
},
};Directive-Based Auth
基于指令的授权
graphql
directive @auth(requires: Role = USER) on FIELD_DEFINITION
type Query {
publicPosts: [Post!]!
me: User @auth
adminDashboard: Dashboard @auth(requires: ADMIN)
}typescript
// Directive implementation
class AuthDirective extends SchemaDirectiveVisitor {
visitFieldDefinition(field) {
const { resolve = defaultFieldResolver } = field;
const requiredRole = this.args.requires;
field.resolve = async function (...args) {
const context = args[2];
if (!context.user) {
throw new AuthenticationError("Not authenticated");
}
if (requiredRole && context.user.role !== requiredRole) {
throw new ForbiddenError("Not authorized");
}
return resolve.apply(this, args);
};
}
}graphql
directive @auth(requires: Role = USER) on FIELD_DEFINITION
type Query {
publicPosts: [Post!]!
me: User @auth
adminDashboard: Dashboard @auth(requires: ADMIN)
}typescript
// Directive implementation
class AuthDirective extends SchemaDirectiveVisitor {
visitFieldDefinition(field) {
const { resolve = defaultFieldResolver } = field;
const requiredRole = this.args.requires;
field.resolve = async function (...args) {
const context = args[2];
if (!context.user) {
throw new AuthenticationError("未认证");
}
if (requiredRole && context.user.role !== requiredRole) {
throw new ForbiddenError("无权限访问");
}
return resolve.apply(this, args);
};
}
}Error Handling
错误处理
Error Types
错误类型
typescript
import {
ApolloError,
AuthenticationError,
ForbiddenError,
UserInputError,
} from "apollo-server";
// Validation errors
throw new UserInputError("Invalid email format", {
field: "email",
});
// Auth errors
throw new AuthenticationError("Must be logged in");
throw new ForbiddenError("Not authorized to view this resource");
// Custom errors
class NotFoundError extends ApolloError {
constructor(resource: string) {
super(`${resource} not found`, "NOT_FOUND");
}
}typescript
import {
ApolloError,
AuthenticationError,
ForbiddenError,
UserInputError,
} from "apollo-server";
// Validation errors
throw new UserInputError("邮箱格式无效", {
field: "email",
});
// Auth errors
throw new AuthenticationError("请先登录");
throw new ForbiddenError("无权限访问该资源");
// Custom errors
class NotFoundError extends ApolloError {
constructor(resource: string) {
super(`${resource} 不存在`, "NOT_FOUND");
}
}Error Formatting
错误格式化
typescript
const server = new ApolloServer({
formatError: (error) => {
// Log internal errors
if (error.extensions?.code === "INTERNAL_SERVER_ERROR") {
console.error(error);
return new Error("Internal server error");
}
// Mask sensitive info
if (error.message.includes("password")) {
return new Error("An error occurred");
}
return error;
},
});typescript
const server = new ApolloServer({
formatError: (error) => {
// Log internal errors
if (error.extensions?.code === "INTERNAL_SERVER_ERROR") {
console.error(error);
return new Error("内部服务器错误");
}
// Mask sensitive info
if (error.message.includes("password")) {
return new Error("发生错误");
}
return error;
},
});Subscriptions
订阅功能
Setup
配置
graphql
type Subscription {
messageCreated(roomId: ID!): Message!
userStatusChanged(userId: ID!): UserStatus!
}typescript
import { PubSub } from "graphql-subscriptions";
const pubsub = new PubSub();
const resolvers = {
Mutation: {
sendMessage: async (_, { input }, context) => {
const message = await createMessage(input);
pubsub.publish(`MESSAGE_CREATED_${input.roomId}`, {
messageCreated: message,
});
return message;
},
},
Subscription: {
messageCreated: {
subscribe: (_, { roomId }) => {
return pubsub.asyncIterator(`MESSAGE_CREATED_${roomId}`);
},
},
},
};graphql
type Subscription {
messageCreated(roomId: ID!): Message!
userStatusChanged(userId: ID!): UserStatus!
}typescript
import { PubSub } from "graphql-subscriptions";
const pubsub = new PubSub();
const resolvers = {
Mutation: {
sendMessage: async (_, { input }, context) => {
const message = await createMessage(input);
pubsub.publish(`MESSAGE_CREATED_${input.roomId}`, {
messageCreated: message,
});
return message;
},
},
Subscription: {
messageCreated: {
subscribe: (_, { roomId }) => {
return pubsub.asyncIterator(`MESSAGE_CREATED_${roomId}`);
},
},
},
};With Filtering
带过滤的订阅
typescript
import { withFilter } from "graphql-subscriptions";
const resolvers = {
Subscription: {
messageCreated: {
subscribe: withFilter(
() => pubsub.asyncIterator("MESSAGE_CREATED"),
(payload, variables, context) => {
// Only send to users in the room
return (
payload.messageCreated.roomId === variables.roomId &&
context.user.rooms.includes(variables.roomId)
);
},
),
},
},
};typescript
import { withFilter } from "graphql-subscriptions";
const resolvers = {
Subscription: {
messageCreated: {
subscribe: withFilter(
() => pubsub.asyncIterator("MESSAGE_CREATED"),
(payload, variables, context) => {
// 仅向房间内用户发送消息
return (
payload.messageCreated.roomId === variables.roomId &&
context.user.rooms.includes(variables.roomId)
);
},
),
},
},
};Client Integration
客户端集成
Apollo Client Setup
Apollo Client 配置
typescript
import { ApolloClient, InMemoryCache, createHttpLink } from "@apollo/client";
import { setContext } from "@apollo/client/link/context";
const httpLink = createHttpLink({
uri: "/graphql",
});
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem("token");
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
},
};
});
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
});typescript
import { ApolloClient, InMemoryCache, createHttpLink } from "@apollo/client";
import { setContext } from "@apollo/client/link/context";
const httpLink = createHttpLink({
uri: "/graphql",
});
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem("token");
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
},
};
});
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
});React Hooks
React Hooks
typescript
import { useQuery, useMutation, gql } from '@apollo/client';
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
`;
function UserProfile({ userId }) {
const { loading, error, data } = useQuery(GET_USER, {
variables: { id: userId },
});
if (loading) return <Spinner />;
if (error) return <Error message={error.message} />;
return <div>{data.user.name}</div>;
}
// Mutation
const CREATE_POST = gql`
mutation CreatePost($input: CreatePostInput!) {
createPost(input: $input) {
post { id title }
errors { field message }
}
}
`;
function CreatePostForm() {
const [createPost, { loading }] = useMutation(CREATE_POST, {
update(cache, { data }) {
// Update cache after mutation
},
});
const handleSubmit = async (input) => {
const { data } = await createPost({ variables: { input } });
if (data.createPost.errors.length) {
// Handle errors
}
};
}typescript
import { useQuery, useMutation, gql } from '@apollo/client';
const GET_USER = gql`
query GetUser($id: ID!) {
user(id: $id) {
id
name
email
}
}
`;
function UserProfile({ userId }) {
const { loading, error, data } = useQuery(GET_USER, {
variables: { id: userId },
});
if (loading) return <Spinner />;
if (error) return <Error message={error.message} />;
return <div>{data.user.name}</div>;
}
// Mutation
const CREATE_POST = gql`
mutation CreatePost($input: CreatePostInput!) {
createPost(input: $input) {
post { id title }
errors { field message }
}
}
`;
function CreatePostForm() {
const [createPost, { loading }] = useMutation(CREATE_POST, {
update(cache, { data }) {
// 变更后更新缓存
},
});
const handleSubmit = async (input) => {
const { data } = await createPost({ variables: { input } });
if (data.createPost.errors.length) {
// 处理错误
}
};
}Best Practices
最佳实践
DO:
建议:
- Design schema from client perspective
- Use input types for mutations
- Return payloads with errors from mutations
- Implement DataLoader for N+1 prevention
- Use cursor-based pagination
- Add query complexity limits
- Version schemas carefully
- 从客户端视角设计Schema
- 变更操作使用输入类型
- 变更操作返回包含错误信息的Payload
- 实现DataLoader解决N+1问题
- 使用基于游标的分页
- 添加查询复杂度限制
- 谨慎进行Schema版本管理
DON'T:
不建议:
- Expose database schema directly
- Create deeply nested types unnecessarily
- Forget about authorization
- Allow unbounded queries
- Skip error handling
- Ignore caching strategies
- Mutate data in queries
- 直接暴露数据库Schema
- 不必要地创建深度嵌套类型
- 忽略授权机制
- 允许无限制的查询
- 跳过错误处理
- 忽略缓存策略
- 在查询中修改数据
Schema Checklist
Schema 检查清单
- Types named descriptively (singular, PascalCase)
- Consistent nullability patterns
- Input types for all mutations
- Payload types with error handling
- Pagination for all lists
- Authentication/authorization considered
- Performance optimizations in place
- 类型命名具有描述性(单数、大驼峰)
- 可空性模式保持一致
- 所有变更操作使用输入类型
- Payload类型包含错误处理
- 所有列表支持分页
- 考虑了认证/授权机制
- 已实施性能优化措施