来源:https://medium.com/techtalkers/a-beginners-guide-to-graphql-12d60d3fba03
由Facebook于2015年发布的GraphQL提供了一种新的、有前途的替代传统REST API的方法。从那时起,包括GitHub、Shopify和Intuit在内的许多公司都在它们的生态系统中采用了GraphQL。虽然REST api可能不会很快过时,但GraphQL正在迅速受到许多开发人员的欢迎和喜爱。因此,对GraphQL的需求急剧增长,并预计在未来十年将呈指数级增长。在本文结束时,您将很好地理解GraphQL的工作方式以及如何使用Node.js构建api。在本教程中,我们将构建一个功能完整的GraphQL API,它将向数组添加和查询“用户”。
注意:在生产环境中,您应该查询和修改数据库(如MongoDB或Firebase),但为了简单起见,在本教程中我们将使用硬编码数据。
前提条件:本教程是为那些对JavaScript和Node.js有很好理解的人准备的。建议您具备事先的GraphQL知识,但不要求您遵循。
一、GraphQL是什么?
GraphQL或“图形查询语言”,顾名思义,是一种用于api的查询语言。
SQL是一种用于管理关系数据库的查询语言,而GraphQL是一种允许客户端(前端)从API请求数据的查询语言。
二、GraphQL的优点
为什么在传统的REST API上使用GraphQL ?让我们来看看GraphQL相对于RESTful对等物所提供的一些优势:
一个端点:对于传统的REST api,您必须基于希望请求的数据创建特定的端点。这使得扩展API变得很困难,因为不久之后,您可能会发现自己不得不管理数十甚至数百条必须记住的路由。
更少的服务器请求:GraphQL允许您使用一个服务器请求进行多个查询和更改。当您的服务器每天只允许有限数量的请求时,这很有用。
声明性数据获取:与REST API不同,GraphQL只获取您实际需要的数据。您需要做的就是指定要返回的字段。
类型系统:GraphQL使用类型系统( type system)来描述数据,这使得开发更加容易。如果你是TypeScript的粉丝,这是双赢的。
自文档化:GraphQL是自文档化的,这意味着GraphQL将自动记录您的所有查询和更改。
GraphQL还有许多值得了解的优点(和缺点)。我建议阅读GraphQL: webab Technology提供的所有你需要知道的东西,以了解更多关于GraphQL的信息(点击https://medium.com/@weblab_tech/graphql-everything-you-need-to-know-58756ff253d8)。
三、什么是 Apollo 服务器?
注意:Apollo服务器也有一个express集成,但在本教程中我们不需要express .js。
因为GraphQL只是一种查询语言,所以我们需要一个库来为我们处理样板代码。幸运的是,这样的库已经存在了。
进入Apollo Server,这是一个Node.js库,它提供了一个简单易用的GraphQL服务器。其他的GraphQL服务器库也存在,比如express-graphql,但是Apollo server允许更好的可伸缩性,并支持更大的社区。Apollo Server还提供了一个整洁的GraphQL接口,用于在开发过程中执行查询和更改。
现在我们已经解决了这个问题,我们终于可以开始构建我们的GraphQL API了。以下代码的链接:https://github.com/advaithmalka/graphql-server-tut
步骤1:安装依赖项
注意:确保你安装了一个代码编辑器,就像Visual Studio code一样。
首先,您需要创建一个包含所有服务器文件的目录。我们可以在终端中这样做:
mkdir graphql-server-tut
cd graphql-server-tut
然后,在我们的项目中初始化NPM:
npm init -y
这会在当前目录中创建一个package.json文件。
现在,我们可以开始为我们的服务器安装所需的依赖:
npm install apollo-server graphql
这里,我们安装了两个必需的依赖项。
apollo-server:允许我们轻松地创建GraphQL服务器。
graphql:apollo-server所需的依赖项。
一旦安装了这些包,我们就可以开始为服务器编程了。
步骤2:创建类型定义
我们首先需要在当前目录中创建index.js文件。当我们完成API时,该文件将作为进入服务器的入口点。
接下来,我们需要从apollo-server的NPM模块中导入ApolloServer和gql。
const { ApolloServer, gql } = require(“apollo-server”);
现在,由于GraphQL是一种类型化语言,我们需要用schema定义数据。
将以下代码放在index.js文件中。
const { ApolloServer, gql } = require("apollo-server");
// typeDefs tell the GraphQL server what data to expect
// Notice the gql tag, this converts your string into GraphQL strings that can be read by Apollo
const typeDefs = gql`
type Query {
hello: String!
randomNubmer: Int!
}
`
// the Query type outlines all the queries that can be called by the client
// hello and randomNumber are the names of the queries
// The exclamation mark (!) tells Apollo Server that a result is required
// Here, we define two queries, one returns a String and another returns a Int
在这里,在导入ApolloServer和gql之后,我们创建了一个包含我们的模式的多行GraphQL字符串。大多数开发人员将他们的模式命名为typedefs,因为当我们稍后初始化ApolloServer时,我们需要将我们的模式传递给具有相同名称的对象键。
在这段代码中有一些关键的事情需要注意:
(1)我们的typedef被传递到gql标签中。这个标记将对我们的类型定义进行消毒,并使Apollo服务器能够读取它们。这也允许在开发过程中自动完成。
(2)查询类型(Query type )列出了服务器可以执行的所有可能查询。
(3)hello和randomNumber是两个不同的查询。
(4)在冒号(:)之后定义返回值的类型。在本例中,hello返回一个字符串类型,而randomNumber返回一个整数类型。
(5)类型后面的感叹号(!)表示返回值是必需的。
步骤3:创建解析器函数
现在,我们需要告诉服务器在调用特定查询时要做什么或返回什么。我们可以通过创建解析器函数来解决这个问题。
将下面的代码复制到你的index.js文件中:
// When a query is called a resolver with the same name is run
// The API returns whatever is returned by the resolver
// We are using arrow functions so the "return" keyword is not required
const resolvers = {
// The name of the resolver must match the name of the query in the typeDefs
Query: {
// When the hello query is invoked "Hello world" should be returned
hello: () => "Hello world!",
// When we call the randomNumber query, it should return a number between 0 and 10
randomNumber: () => Math.round(Math.random() * 10),
},
};
让我们逐行看看这段代码做了什么:
(1)解析器应该匹配我们的类型定义。就像我们在typedef中有一个查询类型一样,我们在解析器中有一个查询对象。
(2)查询对象包含与typedef对应的解析器。(每个查询都有一个名称相同的对应解析器函数)
(3)无论在解析器中返回什么,查询都会返回给客户机。
注意:在现实世界中,解析器通常从数据库中获取数据并返回给客户端。不幸的是,获取和修改数据库超出了本教程的范围。
第四步:把它们放在一起
最后在这里!我们一直在等待的步骤是:该运行服务器了。
首先,我们需要创建一个ApolloServer实例,并传入typeDefs和解析器。
我们可以这样做:
// Create an instance of ApolloServer and pass in our typeDefs and resolvers
const server = new ApolloServer({
// If the object key and value have the same name, you can omit the key
typeDefs,
resolvers,
});
// Start the server at port 8080
server.listen({ port: 8080 }).then(({ url }) => console.log(`GraphQL server running at ${url}`));
让我们来看看这里发生了什么:
首先,我们创建了一个ApolloServer实例(在步骤2中导入),并传入typeDefs和解析器,这是在步骤2和步骤3中创建的。
然后,我们在端口8080(默认为4000)上启动服务器。
这就是你的index.js文件现在的样子:
const { ApolloServer, gql } = require("apollo-server");
// typeDefs tell the GraphQL server what data to expect
// Notice the gql tag, this converts your string into GraphQL strings that can be read by Apollo
const typeDefs = gql`
type Query {
hello: String!
randomNumber: Int!
}
`;
// the Query type outlines all the queries that can be called by the client
// hello and randomNumber are the names of the queries
// The exclamation mark (!) tells Apollo Server that a result is required
// Here, we define two queries, one returns a String and another returns a Int
// When a query is called a resolver with the same name is run
// The API returns whatever is returned by the resolver
// We are using arrow functions so the "return" keyword is not required
const resolvers = {
// The name of the resolver must match the name of the query in the typeDefs
Query: {
// When the hello query is invoked "Hello world" should be returned
hello: () => "Hello world!",
// When we call the randomNumber query, it should return a number between 0 and 10
randomNumber: () => Math.round(Math.random() * 10),
},
};
// Create an instance of ApolloServer and pass in our typeDefs and resolvers
const server = new ApolloServer({
// If the object key and value have the same name, you can omit the key
typeDefs,
resolvers,
});
// Start the server at port 8080
server.listen({ port: 8080 }).then(({ url }) => console.log(`GraphQL server running at ${url}`));
我们的服务器已经准备好了!在终端中,通过键入node index来运行index.js文件。
如果您按照正确的步骤操作,您应该会看到登录到控制台的“GraphQL server running at http://localhost:8080/”。
当你导航到localhost:8080时,你应该看到GraphQL playground:
这个整洁的接口来自于 apollo-server模块,允许您直接对服务器执行查询,而不需要连接前端。
注意:如果您的节点环境被设置为生产环境,那么GraphQL playground将不可用。
使用GraphQL playground时有几个很酷的特性需要注意:
(1)在右侧,您将看到两个选项卡:Schema和Docs。
(2)当API的大小增加时,您可以参考Schema选项卡来查看服务器可以执行的所有可用查询和更改。
(3)还记得我提到过GraphQL是自文档化的吗?您可以在Docs选项卡中看到为您的API生成的文档GraphQL。
(4)GraphQL playground还允许您向查询或突变(query or mutation)添加HTTP头。如果您只想让授权用户使用您的API,这是非常有用的。
四、查询我们的API
现在我们的服务器已经设置好了,我们可以通过GraphQL playground向它发送请求。
要执行查询,请将以下代码粘贴到GraphQL playground中:
query {
hello
randomNumber
}
这里,我们调用前面步骤中设置的两个查询。一旦你点击播放按钮,你应该看到发送回的数据对应于我们的解析器返回的数据:
GraphQL的美妙之处在于,您只返回您指定的内容;如果你删除hello查询,它将不再显示在数据中:
五、创建更高级的API
现在,由于您希望了解一点ApolloServer的工作原理,我们将创建一个能够添加和查询用户的API。这一次,我们不仅可以查询数据,还可以添加数据。
步骤1:创建我们的“数据库”
在本教程中,我们将使用硬编码数据,并将所有数据存储在一个数组中,而不是使用MongoDB或Firebase这样的实际数据库。
首先,我们将创建一个名为users的数组。每个用户都有一个姓、名和电子邮件字段。如果我们愿意,我们可以像这样在数组中包含一些硬编码的数据:
const users = [
{
firstName: "GraphQL",
lastName: "isCool",
email: "[email protected]"
},
];
您可以随意向数组中添加硬编码的数据。
步骤2:设置typeDefs
现在,我们需要一种方法来查询“数据库”中的所有用户。让我们更新我们的typeDefs来允许这个函数:
const typeDefs = gql`
# GraphQL enables us to create our own types
# Notice the "User" type matches the shape of our "database"
type User {
firstName: String!
lastName: String!
email: String!
}
type Query {
hello: String!
randomNumber: Int!
# This query is going to return all the users in our array
# Since our "database" is an array containing objects, we need to create a "User" type
# Brackets around the type indicates the query is returning an array
queryUsers: [User]!
}
`;
这里有几件事需要注意:
(1)queryUsers查询返回一个对象数组(因此有方括号)。
(2)我们可以使用type关键字后跟类型名创建自己的GraphQL类型。
(3)在大括号({})中,我们指定type将返回的字段(我们的用户类型将返回三个字段:firstName、lastName和email。这三个都是字符串,并且是必需的)。
步骤3:配置解析器
我们只需要再添加一行代码来完成查询:
const resolvers = {
Query: {
hello: () => "Hello world!",
randomNumber: () => Math.round(Math.random() * 10),
// queryUsers simply returns our users array
queryUsers: () => users,
},
};
这行新代码创建了一个解析器函数,当调用该函数时,将返回users数组。
步骤4:测试查询
这次我们的问题看起来有点不同:
query {
queryUsers {
firstName
lastName
}
}
当我们调用queryUsers查询时,我们需要指定我们希望API以大括号({})返回哪些字段。上面的代码返回所有三个字段,但是如果客户端只需要每个用户的姓和名,你可以省略电子邮件字段来节省带宽:
向数组中添加用户
如果我们的API只能显示硬编码的用户,那么它就没有多大用处。在本节中,我们还将允许我们的API向数组添加用户。
步骤1:向我们的typeedefs添加一个Mutation
当您执行除从数据库读取(创建、更新、删除)之外的任何其他操作时,都应该使用GraphQL Mutation。
所有Mutation必须是GraphQL Mutation类型:
const typeDefs = gql`
type User {
firstName: String!
lastName: String!
email: String!
}
type Query {
hello: String!
randomNumber: Int!
queryUsers: [User]!
}
# Mutations must be in their own type
type Mutation {
# We are creating a mutation called "addUser" that takes in 3 arguments
# These arguments will be available to our resolver, which will push the new user to the "users" array
# Notice that this mutation will return a single User, which will be the one that was created
addUser(firstName:String!, lastName:String!, email:String!): User!
}
`;
我想从这段代码中记下一些事情:
(1)我们正在创建一个名为addUser的新Mutation
(2)addUser接受三个参数:firstName、lastName和email。所有三个参数都是string类型的,并且是必需的(在括号中指定)
(3)addUser返回一个用户类型:一个包含新用户的姓、名和电子邮件的对象。
步骤2:添加addUser解析器
在我们开始编写解析器之前,让我们计划一下它应该完成什么。
首先,当我们运行该解析器时,它将需要从Mutation中获取firstName、lastName和email参数。然后,它需要将该数据作为一个新对象推送到users数组。最后,我们只返回传递到Mutation中的数据。
注意:在使用真实的数据库时,应该实现try, catch块来处理可能发生的错误。
更新您的解析器以匹配以下内容:
const resolvers = {
Query: {
hello: () => "Hello world!",
randomNumber: () => Math.round(Math.random() * 10),
queryUsers: () => users,
},
// All mutation resolvers must be in the Mutation object; just like our typeDefs
Mutation: {
// Once again notice the name of the resolver matches what we defined in our typeDefs
// The first argument to any resolver is the parent, which is not important to us here
// The second argument, args, is an object containing all the arguments passed to the resolver
addUser: (parent, args) => {
users.push(args); // Push the new user to the users array
return args; // Returns the arguments provided, this is the new user we just added
},
},
};
让我们看看这个解析器会做什么:
(1)就像查询一样,突变必须匹配我们的typeDefs并进入Mutation对象。
(2)每个解析器(不仅仅是Mutation)都可以访问4个参数,您可以在文档中了解更多信息。对于这个解析器,我们只需要第二个参数。
(3)第二个参数args将包含新用户的姓、名和电子邮件。如果您愿意,可以在解析器函数中查看console.log参数args,以查看它包含哪些数据。
(4)因为我们的“数据库”只是一个对象数组,我们可以简单地将args对象推入用户数组。
(5)我们的Mutation需要返回创建的新用户。我们可以通过返回args对象来做到这一点。
就是这样!只需要不到10行代码,我们的服务器现在就可以添加和查询用户了!现在,让我们看看如何调用GraphQL突变。
第四步:呼唤我们的Mutation
调用Mutation非常类似于在GraphQL中调用查询:
mutation {
addUser(
firstName: "John",
lastName: "Doe",
email: "[email protected]"
) {
firstName
lastName
}
}
提示:我将把上面的GraphQL查询放在一个新的GraphQL Playground选项卡中。
注意关于上面的查询的一些事情:
(1)为了表示一个Mutation,我们使用Mutation关键字代替查询。
(2)我们可以通过在圆括号中指定参数来将参数传递给GraphQL查询,就像JavaScript函数一样。
(3)在这里,我们创建一个名为John Doe的新用户,电子邮件为[email protected]。如果你愿意随时改变参数。
(4)与queryUsers查询一样,我们可以选择要返回的字段。请记住,此Mutation只返回创建的新用户。
从Mutation返回的数据应该是这样的:
如果您再次运行queryUsers查询,而没有重启服务器,您应该看到一个新用户添加到数组:
注意:重新启动服务器时,新数据将丢失,因此请确保在实际应用程序中使用数据库。
太棒了!我们的API现在可以查询并向数组中添加用户了!为了挑战自己,我建议给每个用户增加几个字段。比如年龄,生日,喜欢的食物。如果您熟悉MongoDB或Firebase,请尝试将数据库与API集成,而不是将数据存储在数组中。
祝贺您使用Apollo服务器构建了您的第一个GraphQL API !本教程只介绍了GraphQL的功能。还有很多东西需要学习,比如订阅、片段、指令等等!无论您是在寻找REST API的替代方案,还是在寻找试图扩展知识的新开发人员,GraphQL都是一个很好的选择,而且我肯定您会喜欢使用它。
编码快乐!
https://www.howtographql.com/