GraphQL 解析器 #
解析器概述 #
解析器(Resolver)是 GraphQL 服务器处理每个字段的核心逻辑,负责获取数据并返回给客户端。
text
┌─────────────────────────────────────────────────────────────┐
│ 解析器工作流程 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 查询请求: │
│ query { │
│ user(id: "1") { │
│ name │
│ posts { │
│ title │
│ } │
│ } │
│ } │
│ │
│ 解析过程: │
│ ┌─────────────┐ │
│ │ Query.user │ ← 解析 user 字段 │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ User.name │ ← 解析 name 字段 │
│ └─────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ User.posts │ ← 解析 posts 字段 │
│ └──────┬──────┘ │
│ │ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Post.title │ ← 解析 title 字段 │
│ └─────────────┘ │
│ │
└─────────────────────────────────────────────────────────────┘
解析器函数 #
基本结构 #
解析器函数接收四个参数:
javascript
const resolvers = {
Query: {
user: (parent, args, context, info) => {
return {
id: args.id,
name: 'Alice',
email: 'alice@example.com'
};
}
}
};
参数说明 #
text
┌─────────────────────────────────────────────────────────────┐
│ 解析器参数 │
├─────────────────────────────────────────────────────────────┤
│ │
│ fieldName(parent, args, context, info) │
│ │
│ parent:父字段的返回值 │
│ - 根查询时为 undefined │
│ - 嵌套字段时为父对象 │
│ │
│ args:字段参数 │
│ - 包含客户端传递的参数 │
│ - { id: "1", limit: 10 } │
│ │
│ context:共享上下文 │
│ - 所有解析器共享的对象 │
│ - 包含认证信息、数据源等 │
│ │
│ info:查询信息 │
│ - 包含查询的 AST │
│ - 高级用途时使用 │
│ │
└─────────────────────────────────────────────────────────────┘
参数示例 #
javascript
const resolvers = {
Query: {
user: (parent, args, context, info) => {
console.log('parent:', parent);
console.log('args:', args);
console.log('context:', context);
console.log('info:', info);
return getUserById(args.id);
}
},
User: {
posts: (parent, args, context, info) => {
console.log('parent:', parent);
return getPostsByUserId(parent.id, args.limit);
}
}
};
Query 解析器 #
基本查询 #
javascript
const resolvers = {
Query: {
user: (_, { id }) => {
return getUserById(id);
},
users: (_, { limit, offset }) => {
return getUsers(limit, offset);
},
search: (_, { query, type, limit }) => {
return searchAll(query, type, limit);
}
}
};
带过滤的查询 #
javascript
const resolvers = {
Query: {
users: async (_, { filter, orderBy, first, after }, { dataSources }) => {
const { filter: dbFilter, sort, skip, limit } = buildQueryOptions({
filter,
orderBy,
first,
after
});
const users = await dataSources.userAPI.findMany({
filter: dbFilter,
sort,
skip,
limit: limit + 1
});
return buildConnection(users, first);
}
}
};
分页查询 #
javascript
const resolvers = {
Query: {
users: async (_, { first, after, filter, orderBy }, { dataSources }) => {
const cursor = after ? decodeCursor(after) : null;
const users = await dataSources.userAPI.findMany({
filter,
orderBy,
cursor,
limit: first + 1
});
const hasNextPage = users.length > first;
const edges = users.slice(0, first).map(user => ({
node: user,
cursor: encodeCursor(user.id)
}));
return {
edges,
pageInfo: {
hasNextPage,
hasPreviousPage: !!cursor,
startCursor: edges[0]?.cursor,
endCursor: edges[edges.length - 1]?.cursor
},
totalCount: dataSources.userAPI.count(filter)
};
}
}
};
Mutation 解析器 #
创建操作 #
javascript
const resolvers = {
Mutation: {
createUser: async (_, { input }, { dataSources }) => {
const existingUser = await dataSources.userAPI.findByEmail(input.email);
if (existingUser) {
return {
user: null,
success: false,
message: 'Email already exists',
errors: [{
field: 'email',
message: 'This email is already registered',
code: 'DUPLICATE_EMAIL'
}]
};
}
const hashedPassword = await hashPassword(input.password);
const user = await dataSources.userAPI.create({
...input,
password: hashedPassword
});
return {
user,
success: true,
message: 'User created successfully'
};
}
}
};
更新操作 #
javascript
const resolvers = {
Mutation: {
updateUser: async (_, { id, input }, { dataSources, user }) => {
if (!user) {
throw new AuthenticationError('Not authenticated');
}
if (user.id !== id && user.role !== 'ADMIN') {
throw new ForbiddenError('Not authorized');
}
const existingUser = await dataSources.userAPI.findById(id);
if (!existingUser) {
return {
user: null,
success: false,
message: 'User not found'
};
}
const updatedUser = await dataSources.userAPI.update(id, input);
return {
user: updatedUser,
success: true,
message: 'User updated successfully'
};
}
}
};
删除操作 #
javascript
const resolvers = {
Mutation: {
deleteUser: async (_, { id }, { dataSources, user }) => {
if (!user) {
throw new AuthenticationError('Not authenticated');
}
if (user.id !== id && user.role !== 'ADMIN') {
throw new ForbiddenError('Not authorized');
}
const deleted = await dataSources.userAPI.delete(id);
return {
success: deleted,
message: deleted ? 'User deleted successfully' : 'User not found',
deletedId: deleted ? id : null
};
}
}
};
字段解析器 #
关联字段 #
javascript
const resolvers = {
User: {
posts: async (parent, { limit }, { dataSources }) => {
return dataSources.postAPI.findByAuthorId(parent.id, limit);
},
comments: async (parent, { first, after }, { dataSources }) => {
return dataSources.commentAPI.findByUserId(parent.id, first, after);
},
followers: async (parent, _, { dataSources }) => {
return dataSources.userAPI.getFollowers(parent.id);
}
},
Post: {
author: async (parent, _, { dataSources }) => {
return dataSources.userAPI.findById(parent.authorId);
},
comments: async (parent, { first, after }, { dataSources }) => {
return dataSources.commentAPI.findByPostId(parent.id, first, after);
},
tags: (parent) => {
return parent.tags || [];
}
}
};
计算字段 #
javascript
const resolvers = {
User: {
fullName: (parent) => {
return `${parent.firstName} ${parent.lastName}`;
},
age: (parent) => {
if (!parent.birthDate) return null;
const today = new Date();
const birthDate = new Date(parent.birthDate);
let age = today.getFullYear() - birthDate.getFullYear();
const monthDiff = today.getMonth() - birthDate.getMonth();
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < birthDate.getDate())) {
age--;
}
return age;
},
isOnline: (parent) => {
if (!parent.lastSeenAt) return false;
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
return new Date(parent.lastSeenAt) > fiveMinutesAgo;
}
},
Post: {
excerpt: (parent, { length = 150 }) => {
if (!parent.content) return '';
return parent.content.length > length
? parent.content.substring(0, length) + '...'
: parent.content;
},
readingTime: (parent) => {
const wordsPerMinute = 200;
const wordCount = parent.content?.split(/\s+/).length || 0;
return Math.ceil(wordCount / wordsPerMinute);
}
}
};
字段转换 #
javascript
const resolvers = {
User: {
avatar: (parent, { size = 'medium' }) => {
if (!parent.avatarUrl) return null;
const sizes = {
small: 50,
medium: 100,
large: 200
};
return `${parent.avatarUrl}?size=${sizes[size]}`;
},
createdAt: (parent) => {
return parent.createdAt?.toISOString();
}
}
};
上下文(Context) #
创建上下文 #
javascript
const { ApolloServer } = require('apollo-server');
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
const token = req.headers.authorization || '';
const user = getUserFromToken(token);
return {
user,
dataSources: {
userAPI: new UserAPI(),
postAPI: new PostAPI(),
commentAPI: new CommentAPI()
}
};
}
});
使用上下文 #
javascript
const resolvers = {
Query: {
me: (_, __, { user }) => {
return user;
},
users: async (_, { filter }, { dataSources, user }) => {
if (!user) {
throw new AuthenticationError('Not authenticated');
}
return dataSources.userAPI.findMany(filter);
}
},
Mutation: {
createPost: async (_, { input }, { dataSources, user }) => {
if (!user) {
throw new AuthenticationError('Not authenticated');
}
return dataSources.postAPI.create({
...input,
authorId: user.id
});
}
}
};
数据源 #
javascript
const { RESTDataSource } = require('apollo-datasource-rest');
class UserAPI extends RESTDataSource {
constructor() {
super();
this.baseURL = 'https://api.example.com/users/';
}
async findById(id) {
return this.get(`/${id}`);
}
async findMany(filter) {
return this.get('/', filter);
}
async create(user) {
return this.post('/', user);
}
async update(id, user) {
return this.put(`/${id}`, user);
}
async delete(id) {
return this.delete(`/${id}`);
}
}
错误处理 #
抛出错误 #
javascript
const {
AuthenticationError,
ForbiddenError,
UserInputError,
ApolloError
} = require('apollo-server');
const resolvers = {
Query: {
user: async (_, { id }, { user, dataSources }) => {
if (!user) {
throw new AuthenticationError('You must be logged in');
}
const targetUser = await dataSources.userAPI.findById(id);
if (!targetUser) {
throw new UserInputError('User not found', {
invalidArgs: ['id']
});
}
if (user.role !== 'ADMIN' && user.id !== id) {
throw new ForbiddenError('Not authorized to view this user');
}
return targetUser;
}
}
};
自定义错误 #
javascript
class ValidationError extends ApolloError {
constructor(message, field, code = 'VALIDATION_ERROR') {
super(message, code, { field });
}
}
class NotFoundError extends ApolloError {
constructor(message, resource) {
super(message, 'NOT_FOUND', { resource });
}
}
const resolvers = {
Mutation: {
createUser: async (_, { input }, { dataSources }) => {
if (!isValidEmail(input.email)) {
throw new ValidationError('Invalid email format', 'email');
}
const existingUser = await dataSources.userAPI.findByEmail(input.email);
if (existingUser) {
throw new ValidationError('Email already exists', 'email', 'DUPLICATE_EMAIL');
}
return dataSources.userAPI.create(input);
}
}
};
错误格式化 #
javascript
const server = new ApolloServer({
typeDefs,
resolvers,
formatError: (err) => {
console.error(err);
if (err.extensions?.code === 'INTERNAL_SERVER_ERROR') {
return new ApolloError(
'Internal server error',
'INTERNAL_SERVER_ERROR'
);
}
return err;
}
});
性能优化 #
DataLoader #
解决 N+1 查询问题:
javascript
const DataLoader = require('dataloader');
const resolvers = {
User: {
posts: async (user, _, { loaders }) => {
return loaders.postsByUserId.load(user.id);
}
}
};
const server = new ApolloServer({
typeDefs,
resolvers,
context: () => {
return {
loaders: {
postsByUserId: new DataLoader(async (userIds) => {
const posts = await getPostsByUserIds(userIds);
return userIds.map(id =>
posts.filter(post => post.authorId === id)
);
})
}
};
}
});
批量加载 #
javascript
class PostLoader {
constructor() {
this.loader = new DataLoader(async (userIds) => {
const posts = await Post.find({ authorId: { $in: userIds } });
const postsByUserId = {};
posts.forEach(post => {
if (!postsByUserId[post.authorId]) {
postsByUserId[post.authorId] = [];
}
postsByUserId[post.authorId].push(post);
});
return userIds.map(id => postsByUserId[id] || []);
});
}
load(userId) {
return this.loader.load(userId);
}
loadMany(userIds) {
return this.loader.loadMany(userIds);
}
}
缓存 #
javascript
const resolvers = {
Query: {
user: async (_, { id }, { dataSources, cache }) => {
const cacheKey = `user:${id}`;
const cached = await cache.get(cacheKey);
if (cached) {
return cached;
}
const user = await dataSources.userAPI.findById(id);
await cache.set(cacheKey, user, 300);
return user;
}
}
};
查询复杂度 #
javascript
const { createComplexityLimitRule } = require('graphql-validation-complexity');
const server = new ApolloServer({
typeDefs,
resolvers,
validationRules: [
createComplexityLimitRule(1000, {
onCost: (cost) => console.log('Query cost:', cost),
formatErrorMessage: (cost) =>
`Query with cost ${cost} exceeds complexity limit`
})
]
});
订阅解析器 #
基本订阅 #
javascript
const { PubSub } = require('apollo-server');
const pubsub = new PubSub();
const resolvers = {
Subscription: {
onUserCreated: {
subscribe: () => pubsub.asyncIterator(['USER_CREATED'])
},
onPostCreated: {
subscribe: () => pubsub.asyncIterator(['POST_CREATED'])
}
},
Mutation: {
createUser: async (_, { input }, { dataSources }) => {
const user = await dataSources.userAPI.create(input);
pubsub.publish('USER_CREATED', { onUserCreated: user });
return user;
}
}
};
带过滤的订阅 #
javascript
const resolvers = {
Subscription: {
onCommentAdded: {
subscribe: (_, { postId }, { pubsub }) => {
return pubsub.asyncIterator([`COMMENT_ADDED_${postId}`]);
}
}
},
Mutation: {
createComment: async (_, { input }, { dataSources, pubsub }) => {
const comment = await dataSources.commentAPI.create(input);
pubsub.publish(`COMMENT_ADDED_${input.postId}`, {
onCommentAdded: comment
});
return comment;
}
}
};
带认证的订阅 #
javascript
const resolvers = {
Subscription: {
onNotification: {
subscribe: (_, __, { user, pubsub }) => {
if (!user) {
throw new AuthenticationError('Not authenticated');
}
return pubsub.asyncIterator([`NOTIFICATION_${user.id}`]);
}
}
}
};
解析器最佳实践 #
1. 使用数据源 #
javascript
const resolvers = {
Query: {
users: async (_, { filter }, { dataSources }) => {
return dataSources.userAPI.findMany(filter);
}
}
};
2. 使用 DataLoader #
javascript
const resolvers = {
User: {
posts: (user, _, { loaders }) => {
return loaders.postsByUserId.load(user.id);
}
}
};
3. 错误处理 #
javascript
const resolvers = {
Query: {
user: async (_, { id }, { dataSources }) => {
const user = await dataSources.userAPI.findById(id);
if (!user) {
throw new UserInputError('User not found');
}
return user;
}
}
};
4. 权限控制 #
javascript
const resolvers = {
Mutation: {
deleteUser: async (_, { id }, { user, dataSources }) => {
if (!user) {
throw new AuthenticationError('Not authenticated');
}
if (user.role !== 'ADMIN') {
throw new ForbiddenError('Not authorized');
}
return dataSources.userAPI.delete(id);
}
}
};
5. 输入验证 #
javascript
const resolvers = {
Mutation: {
createUser: async (_, { input }, { dataSources }) => {
if (!isValidEmail(input.email)) {
throw new UserInputError('Invalid email', {
invalidArgs: ['email']
});
}
if (input.password.length < 8) {
throw new UserInputError('Password too short', {
invalidArgs: ['password']
});
}
return dataSources.userAPI.create(input);
}
}
};
完整示例 #
javascript
const {
ApolloServer,
AuthenticationError,
ForbiddenError,
UserInputError,
PubSub
} = require('apollo-server');
const DataLoader = require('dataloader');
const pubsub = new PubSub();
const resolvers = {
Query: {
me: (_, __, { user }) => user,
user: async (_, { id }, { dataSources, user }) => {
if (!user) {
throw new AuthenticationError('Not authenticated');
}
const targetUser = await dataSources.userAPI.findById(id);
if (!targetUser) {
throw new UserInputError('User not found');
}
return targetUser;
},
users: async (_, { filter, orderBy, first, after }, { dataSources, user }) => {
if (!user || user.role !== 'ADMIN') {
throw new ForbiddenError('Not authorized');
}
return dataSources.userAPI.findMany({ filter, orderBy, first, after });
},
search: async (_, { query, type, limit }, { dataSources }) => {
return dataSources.searchAPI.search(query, type, limit);
}
},
Mutation: {
createUser: async (_, { input }, { dataSources }) => {
const existingUser = await dataSources.userAPI.findByEmail(input.email);
if (existingUser) {
throw new UserInputError('Email already exists', {
invalidArgs: ['email']
});
}
const user = await dataSources.userAPI.create(input);
pubsub.publish('USER_CREATED', { onUserCreated: user });
return {
user,
success: true,
message: 'User created successfully'
};
},
updateUser: async (_, { id, input }, { dataSources, user }) => {
if (!user) {
throw new AuthenticationError('Not authenticated');
}
if (user.id !== id && user.role !== 'ADMIN') {
throw new ForbiddenError('Not authorized');
}
const updatedUser = await dataSources.userAPI.update(id, input);
return {
user: updatedUser,
success: true,
message: 'User updated successfully'
};
},
deleteUser: async (_, { id }, { dataSources, user }) => {
if (!user) {
throw new AuthenticationError('Not authenticated');
}
if (user.id !== id && user.role !== 'ADMIN') {
throw new ForbiddenError('Not authorized');
}
const deleted = await dataSources.userAPI.delete(id);
return {
success: deleted,
message: deleted ? 'User deleted' : 'User not found',
deletedId: deleted ? id : null
};
}
},
Subscription: {
onUserCreated: {
subscribe: () => pubsub.asyncIterator(['USER_CREATED'])
},
onUserUpdated: {
subscribe: (_, { id }) => pubsub.asyncIterator([`USER_UPDATED_${id}`])
}
},
User: {
posts: async (user, { limit }, { loaders }) => {
return loaders.postsByUserId.load(user.id);
},
fullName: (user) => `${user.firstName} ${user.lastName}`,
age: (user) => {
if (!user.birthDate) return null;
return calculateAge(user.birthDate);
}
},
Post: {
author: async (post, _, { loaders }) => {
return loaders.userById.load(post.authorId);
},
excerpt: (post, { length = 150 }) => {
return post.content?.substring(0, length) || '';
}
}
};
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
const token = req.headers.authorization || '';
const user = getUserFromToken(token);
return {
user,
pubsub,
dataSources: {
userAPI: new UserAPI(),
postAPI: new PostAPI(),
searchAPI: new SearchAPI()
},
loaders: {
userById: new DataLoader(async (ids) => {
const users = await User.find({ _id: { $in: ids } });
return ids.map(id => users.find(u => u.id === id));
}),
postsByUserId: new DataLoader(async (userIds) => {
const posts = await Post.find({ authorId: { $in: userIds } });
return userIds.map(id => posts.filter(p => p.authorId === id));
})
}
};
},
formatError: (err) => {
console.error(err);
return err;
}
});
下一步 #
现在你已经掌握了解析器的实现方法,接下来学习 高级主题,了解认证授权、性能优化、安全防护等进阶内容!
最后更新:2026-03-29