跳到主要内容

GraphQL 服务端

问题

GraphQL 服务端如何设计 Schema 和 Resolver?N+1 问题怎么解决?

答案

GraphQL 服务端基础

graphql-server.ts
import { ApolloServer } from '@apollo/server';
import { startStandaloneServer } from '@apollo/server/standalone';

// Schema 定义
const typeDefs = `#graphql
type User {
id: ID!
name: String!
email: String!
posts: [Post!]!
}

type Post {
id: ID!
title: String!
content: String!
author: User!
}

type Query {
users: [User!]!
user(id: ID!): User
posts(limit: Int, offset: Int): [Post!]!
}

type Mutation {
createPost(title: String!, content: String!): Post!
}
`;

// Resolver
const resolvers = {
Query: {
users: () => db.user.findMany(),
user: (_: unknown, { id }: { id: string }) => db.user.findById(id),
posts: (_: unknown, { limit, offset }: { limit?: number; offset?: number }) =>
db.post.findMany({ take: limit, skip: offset }),
},
User: {
posts: (parent: User) => db.post.findMany({ where: { authorId: parent.id } }),
},
Post: {
author: (parent: Post) => db.user.findById(parent.authorId),
},
Mutation: {
createPost: (_: unknown, args: CreatePostInput, ctx: Context) => {
return db.post.create({ ...args, authorId: ctx.userId });
},
},
};

const server = new ApolloServer({ typeDefs, resolvers });

N+1 问题与 DataLoader

dataloader.ts
import DataLoader from 'dataloader';

// ❌ N+1 问题:查询 10 个 Post,每个 Post 的 author 又查一次
// SELECT * FROM posts LIMIT 10 -- 1 次
// SELECT * FROM users WHERE id = 1 -- N 次
// SELECT * FROM users WHERE id = 2
// ...

// ✅ DataLoader 批量加载
const userLoader = new DataLoader(async (userIds: readonly string[]) => {
const users = await db.user.findMany({
where: { id: { in: [...userIds] } },
});
// 必须按 userIds 顺序返回
const userMap = new Map(users.map(u => [u.id, u]));
return userIds.map(id => userMap.get(id) || null);
});

// Resolver 中使用 DataLoader
const resolvers = {
Post: {
author: (parent: Post) => userLoader.load(parent.authorId),
// DataLoader 会自动将同一个 tick 内的多次 load 合并为一次批量查询
// SELECT * FROM users WHERE id IN (1, 2, 3, ...) -- 只有 1 次
},
};
DataLoader 原理

DataLoader 利用事件循环的微任务机制,将同一帧内的多次 .load() 调用合并为一次批量请求。


常见面试问题

Q1: GraphQL 和 REST 怎么选?

答案

维度RESTGraphQL
数据获取固定结构按需获取
过度获取常见不存在
缓存HTTP 缓存天然支持需要额外工具
学习曲线
适用场景CRUD 为主复杂关联数据、多端

Q2: GraphQL 的安全问题?

答案

  1. 查询深度限制:防止恶意嵌套查询
  2. 查询复杂度限制:给 field 设置 cost
  3. 速率限制:按用户/IP 限制
  4. 关闭 introspection:生产环境禁用 Schema 自省

Q3: GraphQL 的缓存怎么做?

答案

  • 客户端:Apollo Client 自动基于 __typename + id 做归一化缓存
  • 服务端:Persisted Queries(预编译查询)、CDN 缓存 GET 请求

相关链接