Apollo Server 与 Client:全栈 GraphQL 生态

FreeGuideOnline 最新 2026-06-15

Apollo Server 与 Client:构建你的第一个全栈 GraphQL 应用

本教程将引导你从零开始,使用 Apollo Server 搭建 GraphQL 接口,并通过 Apollo Client 在前端消费这些数据。你将掌握这两个核心工具的基本用法,并理解 GraphQL 生态如何在真实项目中协同工作。


章节概览

  1. 环境准备与项目结构
  2. 搭建 Apollo Server
    • 初始化项目并安装依赖
    • 定义 GraphQL Schema
    • 编写 Resolvers(解析器)
    • 启动服务器并测试
  3. 搭建 Apollo Client 前端
    • 创建 React 应用并安装依赖
    • 配置 Apollo Client 实例
    • 编写第一个查询(Query)
    • 处理变更(Mutation)请求
    • 使用片段(Fragment)与缓存管理
  4. 高级话题与最佳实践
    • 错误处理与加载状态
    • 客户端缓存策略
    • 服务端数据源与上下文

环境准备与项目结构

我们将采用前后端分离的架构,根目录下包含 serverclient 两个子文件夹。

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-servergraphql(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.authorAuthor.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}
`;

当使用 useMutationupdate 函数手动更新缓存时,片段可以帮助你精确读取/写入缓存。


高级话题与最佳实践

错误处理与加载状态

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}`);
});

但上述需配合 fromHttpLink 使用,若只是简单应用,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:利用声明式的钩子(useQueryuseMutation)获取/变更数据,内置缓存减少重复请求。
  • 协作流程:服务端负责定义类型和操作,客户端只需按需查询,真正实现了 API 的灵活消费。

你可以将此基础扩展至用户认证、实时订阅(Subscriptions)、文件上传等高级场景。现在,你可以自由修改 Schema、添加更多字段,或引入数据库来实现持久化存储,构建你想要的下一代应用。