Apollo Server 与 Client:全栈 GraphQL 生态
Apollo Server 与 Client:构建你的第一个全栈 GraphQL 应用
本教程将引导你从零开始,使用 Apollo Server 搭建 GraphQL 接口,并通过 Apollo Client 在前端消费这些数据。你将掌握这两个核心工具的基本用法,并理解 GraphQL 生态如何在真实项目中协同工作。
章节概览
- 环境准备与项目结构
- 搭建 Apollo Server
- 初始化项目并安装依赖
- 定义 GraphQL Schema
- 编写 Resolvers(解析器)
- 启动服务器并测试
- 搭建 Apollo Client 前端
- 创建 React 应用并安装依赖
- 配置 Apollo Client 实例
- 编写第一个查询(Query)
- 处理变更(Mutation)请求
- 使用片段(Fragment)与缓存管理
- 高级话题与最佳实践
- 错误处理与加载状态
- 客户端缓存策略
- 服务端数据源与上下文
环境准备与项目结构
我们将采用前后端分离的架构,根目录下包含 server 和 client 两个子文件夹。
mkdir apollo-fullstack
cd apollo-fullstack
mkdir server client
确保你的开发环境已经安装了 Node.js(v16+) 和 npm。
搭建 Apollo Server
1. 初始化项目并安装依赖
进入 server 目录并初始化 package.json:
cd server
npm init -y
安装 apollo-server 和 graphql(Apollo Server 4 已整合至 @apollo/server,本教程使用更稳定的 v3 风格清晰讲解核心概念;若使用 v4,可参阅官方迁移指南,基本原理一致):
npm install apollo-server graphql
2. 定义 GraphQL Schema
创建 src/schema.js,用 SDL(Schema Definition Language)定义类型与操作。
const { gql } = require('apollo-server');
const typeDefs = gql`
type Book {
id: ID!
title: String!
author: Author!
}
type Author {
id: ID!
name: String!
books: [Book]
}
# 根查询类型
type Query {
books: [Book]
book(id: ID!): Book
authors: [Author]
}
# 根变更类型
type Mutation {
addBook(title: String!, authorId: ID!): Book
}
`;
module.exports = typeDefs;
注解:[Book] 表示 Book 数组,! 表示字段不可为 null。Query 定义客户端可读的操作,Mutation 定义写操作。
3. 编写 Resolvers(解析器)
创建 src/resolvers.js,它告诉服务器如何返回 Schema 中定义的每个字段的数据。
// 模拟数据:通常这些数据来自数据库
const authors = [
{ id: '1', name: 'Kate Chopin' },
{ id: '2', name: 'Paul Auster' },
];
const books = [
{ id: '1', title: 'The Awakening', authorId: '1' },
{ id: '2', title: 'City of Glass', authorId: '2' },
];
const resolvers = {
Query: {
books: () => books,
book: (parent, args) => books.find(book => book.id === args.id),
authors: () => authors,
},
// 类型关联解析:当查询 Book 的 author 字段时,如何获取 Author 对象
Book: {
author: (parent) => authors.find(author => author.id === parent.authorId),
},
Author: {
books: (parent) => books.filter(book => book.authorId === parent.id),
},
Mutation: {
addBook: (parent, args) => {
const newBook = {
id: String(books.length + 1),
title: args.title,
authorId: args.authorId,
};
books.push(newBook);
return newBook;
},
},
};
module.exports = resolvers;
关键点:parent(第一个参数)是上级解析器的返回值,args 是客户端传来的参数。通过定义 Book.author 和 Author.books,GraphQL 可以自动解析关联数据,避免 N+1 查询(但生产环境推荐使用 DataLoader 优化)。
4. 启动服务器并测试
创建入口文件 src/index.js:
const { ApolloServer } = require('apollo-server');
const typeDefs = require('./schema');
const resolvers = require('./resolvers');
const server = new ApolloServer({ typeDefs, resolvers });
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
在 package.json 中添加启动脚本:
"scripts": {
"start": "node src/index.js"
}
运行 npm start,浏览器打开 http://localhost:4000,你将看到 Apollo Studio Sandbox,可执行测试查询:
query GetBooks {
books {
title
author {
name
}
}
}
搭建 Apollo Client 前端
1. 创建 React 应用并安装依赖
在根目录下,使用 create-react-app 创建前端项目(若已存在则跳过):
cd ../client
npx create-react-app .
安装 Apollo Client 及 React 集成库:
npm install @apollo/client graphql
2. 配置 Apollo Client 实例
在 src/index.js 中创建 ApolloClient 并用 ApolloProvider 包裹整个应用。
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App';
import { ApolloClient, InMemoryCache, ApolloProvider } from '@apollo/client';
const client = new ApolloClient({
uri: 'http://localhost:4000', // Apollo Server 地址
cache: new InMemoryCache(), // 规范化缓存
});
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<ApolloProvider client={client}>
<App />
</ApolloProvider>
);
3. 编写第一个查询(Query)
修改 src/App.js,使用 useQuery 钩子获取书籍列表。
import React from 'react';
import { useQuery, gql } from '@apollo/client';
const GET_BOOKS = gql`
query GetBooks {
books {
id
title
author {
name
}
}
}
`;
function BookList() {
const { loading, error, data } = useQuery(GET_BOOKS);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return (
<ul>
{data.books.map(book => (
<li key={book.id}>
{book.title} — {book.author.name}
</li>
))}
</ul>
);
}
export default function App() {
return (
<div>
<h1>My Book Shelf</h1>
<BookList />
</div>
);
}
启动客户端 npm start,页面将显示书籍列表。Apollo Client 自动处理请求、缓存和状态管理。
4. 处理变更(Mutation)请求
添加一个表单来新增书籍。继续在 App.js 中实现:
import { useMutation, gql } from '@apollo/client';
const ADD_BOOK = gql`
mutation AddBook($title: String!, $authorId: ID!) {
addBook(title: $title, authorId: $authorId) {
id
title
}
}
`;
function AddBookForm() {
let titleInput;
let authorInput;
const [addBook, { data, loading, error }] = useMutation(ADD_BOOK, {
// 刷新书籍列表缓存
refetchQueries: [{ query: GET_BOOKS }],
});
const handleSubmit = (e) => {
e.preventDefault();
addBook({
variables: {
title: titleInput.value,
authorId: authorInput.value,
},
});
titleInput.value = '';
authorInput.value = '';
};
return (
<form onSubmit={handleSubmit}>
<input ref={node => (titleInput = node)} placeholder="书名" />
<input ref={node => (authorInput = node)} placeholder="作者ID" />
<button type="submit">添加</button>
{loading && <p>Adding...</p>}
{error && <p>Error: {error.message}</p>}
</form>
);
}
// 将 AddBookForm 引入 App 组件中
export default function App() {
return (
<div>
<h1>My Book Shelf</h1>
<AddBookForm />
<BookList />
</div>
);
}
注意:refetchQueries 是刷新数据的快捷方式,但生产环境中更推荐直接更新缓存(update 回调)以避免不必要的网络请求。
5. 使用片段(Fragment)与缓存管理
定义可复用的片段,确保数据一致性。创建 src/fragments.js:
import { gql } from '@apollo/client';
export const BOOK_DETAILS = gql`
fragment BookDetails on Book {
id
title
author {
name
}
}
`;
在查询和变更中引用片段:
const GET_BOOKS = gql`
query GetBooks {
books {
...BookDetails
}
}
${BOOK_DETAILS}
`;
当使用 useMutation 的 update 函数手动更新缓存时,片段可以帮助你精确读取/写入缓存。
高级话题与最佳实践
错误处理与加载状态
Apollo Client 提供了 onError 链接和 errorPolicy 选项。例如,在创建 Apollo Client 时添加错误处理:
import { onError } from '@apollo/client/link/error';
const errorLink = onError(({ graphQLErrors, networkError }) => {
if (graphQLErrors)
graphQLErrors.forEach(({ message, locations, path }) =>
console.log(`[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`)
);
if (networkError) console.log(`[Network error]: ${networkError}`);
});
但上述需配合 from 和 HttpLink 使用,若只是简单应用,useQuery/useMutation 返回的 error 对象已足够。
客户端缓存策略
InMemoryCache 提供字段策略(type policies)来定制缓存行为。例如,对于某个列表字段,可以定义合并(merge)逻辑,防止分页时覆盖已有数据:
const cache = new InMemoryCache({
typePolicies: {
Query: {
fields: {
books: {
merge(existing = [], incoming) {
return incoming; // 简单替换;真实场景可采用偏移量分页策略
},
},
},
},
},
});
服务端数据源与上下文
Apollo Server 支持通过 context 传递数据库连接、用户认证信息等。在 ApolloServer 构造时加入:
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
// 从 req 中解析 token,获取用户
const token = req.headers.authorization || '';
// 返回供所有 resolver 使用的对象
return { token, db: someDatabaseConnection };
},
});
在 resolver 中通过第三个参数 context 获取:(parent, args, context) => { ... }。
对于实际数据源,推荐使用 RESTDataSource 或数据库 ORM,将其注入 context 统一管理。
总结
至此,你已经完成了一个基于 Apollo Server 和 Apollo Client 的全栈 GraphQL 应用。核心要点回顾:
- Apollo Server:通过 Schema + Resolvers 定义数据图,直观管理类型关联。
- Apollo Client:利用声明式的钩子(
useQuery、useMutation)获取/变更数据,内置缓存减少重复请求。 - 协作流程:服务端负责定义类型和操作,客户端只需按需查询,真正实现了 API 的灵活消费。
你可以将此基础扩展至用户认证、实时订阅(Subscriptions)、文件上传等高级场景。现在,你可以自由修改 Schema、添加更多字段,或引入数据库来实现持久化存储,构建你想要的下一代应用。