1186 字
6 分钟
GraphQL 实战指南:从 Schema 设计到性能优化的完整教程
GraphQL 实战指南:从 Schema 设计到性能优化的完整教程
🚀 前言:GraphQL 是一种用于 API 的查询语言,它允许客户端精确地声明需要的数据,避免了 REST API 的过度获取和获取不足问题。这篇文章将带你从入门到实战,掌握 GraphQL 开发。
一、GraphQL 核心概念
1.1 与 REST 的对比
REST API 的问题:
- 过度获取:获取了不需要的字段
- 获取不足:需要多次请求才能获取完整数据
- 版本管理:API 版本迭代困难
GraphQL 的优势:
- 精确获取:只获取需要的字段
- 一次请求:通过嵌套查询获取关联数据
- 强类型:Schema 定义了严格的类型系统
- 无需版本:通过 Schema 演进
1.2 Schema 定义
# 定义类型type User { id: ID! name: String! email: String! posts: [Post!]!}
type Post { id: ID! title: String! content: String! author: User! createdAt: String!}
# 定义查询type Query { user(id: ID!): User users: [User!]! post(id: ID!): Post posts(authorId: ID): [Post!]!}
# 定义变更type Mutation { createUser(input: CreateUserInput!): User! updateUser(id: ID!, input: UpdateUserInput!): User! deleteUser(id: ID!): Boolean! createPost(input: CreatePostInput!): Post!}
# 输入类型input CreateUserInput { name: String! email: String!}
input UpdateUserInput { name: String email: String}
input CreatePostInput { title: String! content: String! authorId: ID!}1.3 查询语法
# 基本查询query { user(id: "1") { id name email }}
# 嵌套查询query { user(id: "1") { name posts { title createdAt } }}
# 带参数的查询query GetUser($id: ID!) { user(id: $id) { name email }}
# 变更mutation CreateUser($input: CreateUserInput!) { createUser(input: $input) { id name email }}
# 片段复用fragment UserFields on User { id name email}
query { user(id: "1") { ...UserFields posts { title } }}二、后端实现
2.1 Apollo Server
import { ApolloServer } from '@apollo/server';import { startStandaloneServer } from '@apollo/server/standalone';
// 定义 Schemaconst typeDefs = `#graphql type Book { title: String author: String }
type Query { books: [Book] }`;
// 模拟数据const books = [ { title: 'The Awakening', author: 'Kate Chopin' }, { title: 'City of Glass', author: 'Paul Auster' }];
// 定义 Resolversconst resolvers = { Query: { books: () => books, },};
// 创建服务器const server = new ApolloServer({ typeDefs, resolvers,});
const { url } = await startStandaloneServer(server, { listen: { port: 4000 },});
console.log(`Server ready at: ${url}`);2.2 复杂 Resolver
const resolvers = { Query: { user: async (_, { id }, { dataSources }) => { return dataSources.userAPI.getUser(id); }, users: async (_, __, { dataSources }) => { return dataSources.userAPI.getUsers(); } },
User: { // 为 User 类型的 posts 字段提供解析 posts: async (parent, _, { dataSources }) => { return dataSources.postAPI.getPostsByAuthor(parent.id); },
// 计算字段 fullName: (parent) => `${parent.firstName} ${parent.lastName}` },
Mutation: { createPost: async (_, { input }, { dataSources, user }) => { if (!user) throw new AuthenticationError('Not authenticated');
return dataSources.postAPI.createPost({ ...input, authorId: user.id }); } }};2.3 DataLoader 解决 N+1 问题
import DataLoader from 'dataloader';
// 批量加载函数const batchUsers = async (ids) => { const users = await db.users.findMany({ where: { id: { in: ids } } });
// 按原始顺序返回 return ids.map(id => users.find(user => user.id === id));};
// 创建 DataLoaderconst userLoader = new DataLoader(batchUsers);
// 在 Resolver 中使用const resolvers = { Post: { author: (post, _, { loaders }) => { return loaders.user.load(post.authorId); } }};
// 上下文const context = () => ({ loaders: { user: new DataLoader(batchUsers) }});三、前端使用 Apollo Client
3.1 客户端配置
import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client';import { setContext } from '@apollo/client/link/context';
const httpLink = createHttpLink({ uri: 'http://localhost:4000/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({ typePolicies: { Query: { fields: { users: { merge(existing, incoming) { return incoming; } } } } } })});3.2 Vue 中使用
<script setup>import { useQuery, useMutation } from '@vue/apollo-composable';import gql from 'graphql-tag';
// 查询const { result, loading, error } = useQuery(gql` query GetUsers { users { id name email } }`);
// 变更const { mutate: createUser, onDone } = useMutation(gql` mutation CreateUser($input: CreateUserInput!) { createUser(input: $input) { id name } }`);
const handleCreate = async () => { await createUser({ input: { name: '张三', email: 'zhangsan@example.com' } });};</script>四、性能优化
4.1 查询复杂度限制
import { createComplexityLimitRule } from 'graphql-validation-complexity';
const complexityLimit = createComplexityLimitRule(1000);
const server = new ApolloServer({ typeDefs, resolvers, validationRules: [complexityLimit]});4.2 持久化查询
// 使用 Apollo Persisted Queriesimport { createPersistedQueryLink } from '@apollo/client/link/persisted-queries';import { sha256 } from 'crypto-hash';
const link = createPersistedQueryLink({ sha256, useGETForHashedQueries: true});4.3 缓存策略
const cache = new InMemoryCache({ typePolicies: { Post: { fields: { comments: { merge(existing = [], incoming) { return [...existing, ...incoming]; } } } } }});五、订阅(Subscription)
// Schematype Subscription { postAdded: Post}
// Resolverimport { PubSub } from 'graphql-subscriptions';
const pubsub = new PubSub();
const resolvers = { Subscription: { postAdded: { subscribe: () => pubsub.asyncIterator(['POST_ADDED']) } },
Mutation: { createPost: async (_, { input }) => { const post = await db.posts.create(input); pubsub.publish('POST_ADDED', { postAdded: post }); return post; } }};六、总结
GraphQL 带来了 API 开发的新范式:
- 精确数据获取:客户端决定需要什么数据
- 强类型系统:Schema 定义了严格的类型
- 单一端点:所有操作通过一个端点完成
- 强大的工具链:Apollo、Relay 等完善的生态
- 实时更新:Subscription 支持实时数据推送
GraphQL 特别适合复杂的数据关系场景,如社交网络、电商系统等。掌握 GraphQL 将帮助你构建更灵活、更高效的 API!
GraphQL 实战指南:从 Schema 设计到性能优化的完整教程
https://www.oferry.com/posts/a83/