发布于 2026-01-06 1 阅读
0

如何开始使用 GraphQL、React、Apollo Client 和 Apollo Server 应用程序

如何开始使用 GraphQL、React、Apollo Client 和 Apollo Server 应用程序

这是一个分为两部分的系列文章。在第一部分中,我们将学习 GraphQL 的概念及其优势,并使用 GraphQL 构建后端。在第二部分中,我们将学习如何使用 Apollo Client 将 GraphQL 后端集成到 React 前端服务中。本系列文章最初发布在我的个人博客上。您可以在下方找到两部分的链接。

  1. 如何开始使用 GraphQL、React、Apollo Client 和 Apollo Server 应用程序
  2. 如何开始使用 GraphQL、React、Apollo Client 和 Apollo Server 开发应用程序 - 第二部分

GraphQL 已经存在相当长一段时间了,我们常常觉得它很复杂,但实际上,GraphQL 只是一套规范,用于定义服务器和客户端之间如何通过 HTTP 协议交换数据。它本质上是一种 API 查询语言,定义了可以从服务器获取哪些数据。这与你可能使用过的标准 API 截然不同,标准 API 中通常有一个特定的端点来获取特定数据。例如,在 Medium API 中,我们可能有一个 API/api/allarticles/:userId可以返回特定用户的所有文章。这种构建 API 的方式被称为 REST API,我们已经使用这种技术构建 API 很长时间了。在此之前,我们使用 SOAP,它使用 XML 数据结构。GraphQL 的独特之处在于它如何改进 REST 的理念。在 REST 中,我们访问一个 URL 并获取一些数据;而在 GraphQL 中,我们可以明确地请求我们想要查找的内容,并仅获取我们想要构建特定页面所需的特定子集。

入门

现在,简短介绍之后,让我们直接进入演示环节。在这个演示中,我们将重点使用 Apollo Client(一个适用于所有主流前端 JavaScript 框架的 GraphQL 客户端库)和 Apollo Server(用于构建后端)来构建一个小型 React 应用程序。本教程的所有代码都可以在 GitHub 上找到。那么,让我们开始构建一个简单的应用程序吧。

本次演示将重点介绍如何使用 ReactJs 构建一个简单的应用程序,并结合 Apollo Client 前端和 Apollo Server 构建一个轻量级的 GraphQL 后端。首先,我们来设置一个简单的文件夹结构。为了简化本入门指南,我们将后端和前端都放在同一个文件夹中。那么,让我们开始吧。

基本项目设置命令

基本文件夹结构

现在,在设置好文件夹结构之后,我们将首先构建后端,然后再构建 React 前端来展示我们的数据。


使用 Apollo GraphQL 构建后端服务

基本后端设置命令

后端服务的文件夹结构

现在,初始文件夹已经创建完成,让我们开始编写一些代码,并学习一些关于 Apollo 服务器的知识。那么,让我们直接进入index.js文件,使用最基本的配置初始化服务器。

const {ApolloServer, gql} = require('apollo-server');

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

server.listen()
    .then(({url}) => {
      console.log(`Server ready at ${url}`);
    })
    .catch(err => {console.log(err)})
Enter fullscreen mode Exit fullscreen mode

现在,在继续之前,让我们先分析一下目前为止编写的 12 行代码,看看我们正在处理什么。大部分代码都很简单明了,除了我们看到的名为 ` typeDefsand` 和 ` .` 的变量。所以,我们先来探究一下 ` and` 和 ` resolvers.` 究竟是什么。typeDefsresolvers

每个 GraphQL 服务器都需要定义客户端可以访问的数据,这可以通过模式(schema)来实现,这些模式存储在我们的typeDefs文件中。现在,每个模式可以包含三个根操作:`fetch` Query、` create`Mutation和 `update` subscription。它们各自都有特定的用途。`fetch`Query通常用于获取数据库中已存在的数据,` Mutationcreate` 用于创建或更新任何数据,而Subscription`subscription` 用于监听 GraphQL 服务器生成的事件。订阅依赖于发布/订阅原语来生成通知订阅的事件。

现在,我们已经完成了对Query` Mutationget` 和 `create`的一些基本介绍Subscription。类似地,`get`resolver本质上是一个函数或方法,用于解析模式中某个字段的值。它们负责执行所有任务,包括获取数据、创建数据以及运行一些业务逻辑来解析客户端请求的字段。接下来,让我们来看一些示例,了解如何将它们结合使用来创建我们的 GraphQL 服务器。

现在,让我们继续我们的示例应用程序。我个人更喜欢将我的应用程序分开resolverstypeDefs所以让我们为应用程序resolvers和应用程序分别创建文件typeDefs

创建类型定义和解析器文件

文件创建完成后,我们来看看新的文件夹结构,然后就可以开始使用了,typeDefs因为typeDefs它们本质上就像客户端的接口,客户端可以通过这些接口向服务器请求数据。那么,让我们从创建第一个文件开始typeDefs

添加 typeDefs 和 resolvers 文件后的新文件夹结构

正如我之前所说,这typeDefs是客户端连接到我们的后端服务并请求数据的方式。那么,让我们看看如何定义它。

const {gql} = require('apollo-server');


const typeDefs = gql`
        type Query {
            sayHello: String
        }

`

module.exports = typeDefs
Enter fullscreen mode Exit fullscreen mode

在上面的例子中,我们定义了一个简单的查询Query,它可以帮助我们从后端获取一些数据。在本例中,它sayHello返回一个String由查询本身定义的类型sayHello。请确保你的查询名称具有自声明性。这里,我们的Query名称清晰地表明了它将要执行的操作。既然我们已经定义了查询,typeDefs我们还需要resolver针对这个查询定义一个函数,该函数将实际解析或计算一些值。GraphQL 实现这一点的方式是将每个typeDefs名称映射到相应的resolver函数名称。因此,在本例中,我们需要定义一个同名的解析器。让我们也来定义它。

const resolvers = {
  Query: {
    sayHello: () => 'hello random person',
  },
};

module.exports = resolvers
Enter fullscreen mode Exit fullscreen mode

sayHello这里我们在内部定义了函数Query,在本例中它会解析为某个值hello random person。请确保resolver函数的返回类型正确,typeDefs否则查询将返回空值null。现在,由于我们已经创建了 ` <function_name>`typeDefs和 ` <function_name> resolvers` 文件,我们只需要对 `<function_name>` 文件稍作修改即可index.js。我们只需将 `<function_name>`resolverstypeDefs`<function_name>` 文件导入到 `index.js` 文件中并使用它们即可。

const {ApolloServer} = require('apollo-server');
const typeDefs = require('./typeDefs')
const resolvers = require('./resolvers')
const server = new ApolloServer({
  typeDefs,
  resolvers,
});

server.listen()
    .then(({url}) => {
      console.log(`Server ready at ${url}`);
      ``
    })
    .catch(err => {console.log(err)})
Enter fullscreen mode Exit fullscreen mode

现在,介绍部分已经结束,让我们构建一个简单的待办事项列表,开始使用 GraphQL 进行 CRUD 操作。这里我们不使用实际的数据库,而是在后端服务中创建一个虚拟数据库对象json,通过操作该对象来执行 CRUD 操作。那么,让我们创建这个虚拟 JSON 文件吧。

创建 fake_data.js 文件

const DAILY_TASKS = [
  {
    task: "Make Coffee",
    completed: false,
    id: 1
  },
  {
    task: "Learn GraphQl",
    completed: false,
    id: 2
  },
  {
    task: "Learn GoLang",
    completed: false,
    id: 3
  },
  {
    task: "Learn NodeJs",
    completed: false,
    id: 4
  },
  {
    task: "Learn GraphQl",
    completed: false,
    id: 5
  }
];

module.exports = DAILY_TASKS;

Enter fullscreen mode Exit fullscreen mode

现在,我们将有 3 个 Mutation 来更新、创建和删除我们伪 JSON 文件中的数据,以及 1 个查询来交互和获取我们的数据。

现在,让我们创建第一个Query用于从后端服务获取数据的函数。我们称之为fetchTasks

const { gql } = require("apollo-server");

const typeDefs = gql`
  type Tasks {
    task: String
    id: ID
    completed: Boolean
  }
  type Query {
    fetchTasks: Tasks
  }
`;

module.exports = typeDefs;

Enter fullscreen mode Exit fullscreen mode

这里我们定义了获取任务Query,它的返回类型为Tasks。现在让我们为新添加的查询编写一个解析器函数。

const DAILY_TASKS = require("./fake_data");
const resolvers = {
  Query: {
    fetchTasks: () => DAILY_TASKS[0]
  }
};

module.exports = resolvers;

Enter fullscreen mode Exit fullscreen mode

这里我们的查询将始终返回第一个任务。在更新此行为之前,让我们先运行一下服务器。

运行我们的服务器

现在让我们导航到原始的`http://localhost:4000/` endraw。

http://localhost:4000/

现在,当我们访问http://localhost:4000/ 时,会看到这个图形用户界面。这就是 GraphQL Playground,我们可以在这里运行查询。让我们在Query这里运行第一个查询。

运行我们的第一个查询

现在,运行第一个查询后,我们可以看到结果,它从后端获取了数据,这些数据存储在我们预先准备好的 JSON 文件中。接下来,让我们为函数添加一些逻辑,并接受客户端提供的数据作为筛选条件。

const { gql } = require("apollo-server");

const typeDefs = gql`
  type Tasks {
    task: String
    id: ID
    completed: Boolean
  }

  input fetchTaskFilter {
    id: ID!
  }

  input addTaskInput {
    name: String!
    completed: Boolean!
  }

  input updateTaskInput {
    id: ID!
    name: String
    completed: Boolean
  }

  type Query {
    fetchTask(filter: fetchTaskFilter): Tasks
    fetchTasks: [Tasks]
  }

  type Mutation {
    addTask(input: addTaskInput): Tasks
    updateTask(input: updateTaskInput): Tasks
  }
`;

module.exports = typeDefs;

Enter fullscreen mode Exit fullscreen mode

在上面的例子中,我们已经定义了用于与数据交互的 mutation 和 query。现在,我们注意到!数据类型前面有一个标记,这意味着该字段是必填项,否则我们无法在后端执行 query 或 mutation。接下来,让我们为解析器添加一些逻辑,以便与数据交互。解析器文件中的每个解析器函数都会接收 4 个函数参数,几乎所有 GraphQL 服务器都会以某种形式在解析器中接收这 4 个函数参数。

  • 根类型— 来自前一个/父类型的结果。
  • args — 客户端提供给字段的参数。例如,在我们的typeDefs示例addTask(input:addTaskInput),args 将是{input:{name:"some name",completed:false}}
  • context是一个可变对象,提供给所有解析器。它主要包含身份验证、授权状态以及解析查询时需要考虑的其他任何信息。您可以访问此request对象,以便应用任何中间件,并通过 context 将这些信息提供给解析器。
  • info — 与查询相关的字段特定信息。此参数仅在高级查询中使用,但它包含有关查询执行状态的信息,包括字段名称、字段从根目录到该字段的路径等等。

在这里,我们将主要关注参数,以便访问我们的客户或 Playground 发送的字段。

const DAILY_TASKS = require("./fake_data");
const resolvers = {
  Query: {
    fetchTask: (parent, args, context, info) => {
      return DAILY_TASKS[args.input.id];
    },
    fetchTasks: (parent, args, context, info) => {
      return DAILY_TASKS;
    }
  },
  Mutation: {
    addTask: (parent, args, context, info) => {
      const {
        input: { name, completed }
      } = args;
      const nextId = DAILY_TASKS[DAILY_TASKS.length - 1].id + 1;
      const newTask = {
        task: name,
        completed: completed,
        id: nextId
      };
      DAILY_TASKS.push(newTask);
      return newTask;
    },
    updateTask: (parent, args, context, info) => {
      const {
        input: { id, name, completed }
      } = args;
      const updateTask = DAILY_TASKS.filter(task => {
        return task.id == id;
      });
      if (name) {
        updateTask[0].task = task;
      }
      if (completed) {
        updateTask[0].completed = completed;
      }
      DAILY_TASKS.push(updateTask);
      return updateTask[0];
    }
  }
};

module.exports = resolvers;

Enter fullscreen mode Exit fullscreen mode

现在,我们添加了一些简单的逻辑来与我们的模拟数据库进行交互。接下来,让我们看看如何通过我们的演示环境进行交互。

GraphQL Playground

现在,我们可以看到这里所有的变更和查询。接下来,我们运行一些变更和查询,看看是否有效。

运行 addTask 查询

我们已经完成了服务器的搭建,并且只进行了最少的配置。在本文的第二部分,我们将使用 React 和 Apollo Client 来构建前端客户端,并使用我们刚刚构建的 API。

文章来源:https://dev.to/thakursachin467/how-to-get-started-with-a-graph-ql-react-apollo-client-and-apollo-server-app-2l7b