如何开始使用 GraphQL、React、Apollo Client 和 Apollo Server 应用程序
这是一个分为两部分的系列文章。在第一部分中,我们将学习 GraphQL 的概念及其优势,并使用 GraphQL 构建后端。在第二部分中,我们将学习如何使用 Apollo Client 将 GraphQL 后端集成到 React 前端服务中。本系列文章最初发布在我的个人博客上。您可以在下方找到两部分的链接。
- 如何开始使用 GraphQL、React、Apollo Client 和 Apollo Server 应用程序
- 如何开始使用 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)})
现在,在继续之前,让我们先分析一下目前为止编写的 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 服务器。
现在,让我们继续我们的示例应用程序。我个人更喜欢将我的应用程序分开resolvers,typeDefs所以让我们为应用程序resolvers和应用程序分别创建文件typeDefs。
文件创建完成后,我们来看看新的文件夹结构,然后就可以开始使用了,typeDefs因为typeDefs它们本质上就像客户端的接口,客户端可以通过这些接口向服务器请求数据。那么,让我们从创建第一个文件开始typeDefs。
正如我之前所说,这typeDefs是客户端连接到我们的后端服务并请求数据的方式。那么,让我们看看如何定义它。
const {gql} = require('apollo-server');
const typeDefs = gql`
type Query {
sayHello: String
}
`
module.exports = typeDefs
在上面的例子中,我们定义了一个简单的查询Query,它可以帮助我们从后端获取一些数据。在本例中,它sayHello返回一个String由查询本身定义的类型sayHello。请确保你的查询名称具有自声明性。这里,我们的Query名称清晰地表明了它将要执行的操作。既然我们已经定义了查询,typeDefs我们还需要resolver针对这个查询定义一个函数,该函数将实际解析或计算一些值。GraphQL 实现这一点的方式是将每个typeDefs名称映射到相应的resolver函数名称。因此,在本例中,我们需要定义一个同名的解析器。让我们也来定义它。
const resolvers = {
Query: {
sayHello: () => 'hello random person',
},
};
module.exports = resolvers
sayHello这里我们在内部定义了函数Query,在本例中它会解析为某个值hello random person。请确保resolver函数的返回类型正确,typeDefs否则查询将返回空值null。现在,由于我们已经创建了 ` <function_name>`typeDefs和 ` <function_name> resolvers` 文件,我们只需要对 `<function_name>` 文件稍作修改即可index.js。我们只需将 `<function_name>`resolvers和typeDefs`<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)})
现在,介绍部分已经结束,让我们构建一个简单的待办事项列表,开始使用 GraphQL 进行 CRUD 操作。这里我们不使用实际的数据库,而是在后端服务中创建一个虚拟数据库对象json,通过操作该对象来执行 CRUD 操作。那么,让我们创建这个虚拟 JSON 文件吧。
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;
现在,我们将有 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;
这里我们定义了获取任务Query,它的返回类型为Tasks。现在让我们为新添加的查询编写一个解析器函数。
const DAILY_TASKS = require("./fake_data");
const resolvers = {
Query: {
fetchTasks: () => DAILY_TASKS[0]
}
};
module.exports = resolvers;
这里我们的查询将始终返回第一个任务。在更新此行为之前,让我们先运行一下服务器。
现在,当我们访问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;
在上面的例子中,我们已经定义了用于与数据交互的 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;
现在,我们添加了一些简单的逻辑来与我们的模拟数据库进行交互。接下来,让我们看看如何通过我们的演示环境进行交互。
现在,我们可以看到这里所有的变更和查询。接下来,我们运行一些变更和查询,看看是否有效。
我们已经完成了服务器的搭建,并且只进行了最少的配置。在本文的第二部分,我们将使用 React 和 Apollo Client 来构建前端客户端,并使用我们刚刚构建的 API。
文章来源:https://dev.to/thakursachin467/how-to-get-started-with-a-graph-ql-react-apollo-client-and-apollo-server-app-2l7b












