vlambda博客
学习文章列表

【第1529期】GraphQL入门指南

前言

渐渐也在业界有看到GraphQL的一些实战。今日早读文章由TutorABC@May翻译授权分享。

正文从这开始~~

现如今最常讨论的技术之一便是 API,但是很多人并不知道什么是 API,简单的来说,API 代表应用程序编程接口(Application Programming Interface)。顾名思义,就是为开发者、用户、消费者提供数据交互的接口。

你可以将 API 视为调酒师,你向调酒师请求一杯酒,然后他为你调出你想要的酒。这看似非常简单,但是为什么会存在问题呢?

自现代网络发展以来,构建 API 并不像听起来那么难,但是学习和理解 API 却不是一件简单的事。很多开发者会使用 API 来构建某些内容,或者仅仅是使用数据。因此,API 应该尽可能简洁、直观。经过精心设计的 API 会非常易于学习和使用,同时也会很直观,所以在开始设计 API 时一定要记住这点。

一直以来我们都在使用 REST 来构建 API,随之也带来了一些问题。使用 REST 来设计构建 API 时,你将会面临以下问题:

  • 你需要访问很多资源路径

  • 不易于开发者学习和理解 API

  • 存在信息过度或不足的问题

为了解决这些问题,Facebook 创建了 GraphQL。现如今,我认为 GraphQL 是构建 API 的最佳方式,本文将会告诉你为什么我们现在需要学习 GraphQL。

通过这篇文章,你将学习到 GraphQL 的工作原理,同时我将向你展示如何使用 GraphQL创建设计良好、高效以及功能强大的 API。

在这之前你可能已经听过 GraphQL,因为有很多人和很多公司已经在使用 GraphQL。GraphQL 是开源的,因此它的社区已经变得非常强大。

那么现在,是时候开始在实践中学习 GraphQL 是如何工作的了,以及感受它的魔力啦~

什么是 GraphQL?

GraphQL(https://graphql.org/) 是 Facebook开发的一种开源查询语言。它为我们提供了一种更有效的方法来设计、创建和使用我们的 API,可以说它是 REST 的替代品。

GraphQL 有很多功能,例如:

  • 你可以定义你想要的数据,并获取你所需要的数据,不再像我们使用REST那样获取冗余数据。

  • 它给我们提供单一的 API 资源路径,API 升级不再需要区分版本。

  • GraphQL 是强类型的,你可以在执行之前在 GraphQL 类型系统中验证查询,它可以帮助我们构建更健壮的 API。

这是一篇关于 GraphQL 为什么如此强大以及为何 GraphQL 如今获得了大量人气的基础介绍。如果你想了解更多关于 GraphQL 的信息,我建议你查看 GraphQL(https://graphql.org/) 官网并查看它。

开始

本文的主要目的不是学习如何配置 GraphQL 服务器,所以我们现在还没有深入研究。本文的主要目的在于了解 GraphQL 在实践中的工作原理,因此我们将使用一个名为 Graphpack (https://github.com/glennreyes/graphpack) 的服务器。

在开始我们的项目前,我们需要创建一个新的文件夹,你可以随意命名,我打算将它命名为 graphql-server。

打开终端并输入:

mkdir graphql-server

现在,你需要在你的电脑上安装 npm 或 yarn,npm 和 yarn 是编程语言 JavaScript 的包管理器,对于 Node.js,默认包管理器是 npm。

进入你创建的文件夹,输入以下命令:

npm init -y

如果你使用的是 yarn,则输入以下命令:

yarn init

npm 将会为你自动创建一个 package.json 文件,你安装的所有依赖和命令都会在这个文件中。

现在,我们需要安装我们将要使用的唯一依赖项——Graphpack。

Graphpack 允许你创建零配置的 GraphQL 服务器,对于刚开始学习 GraphQL的我们来说这将帮助我们学习更多其他的内容,而不必担心复杂的服务器配置。

进入项目的根目录,在终端输入以下命令:

npm install --save-dev graphpack

如果你使用的是yarn,则输入以下命令:

yarn add --dev graphpack

安装完 graphpack 之后,找到 package.json 文件中 scripts 配置项,加入如下代码:

"scripts": {
   
"dev": "graphpack",
   
"build": "graphpack build"
}

我们将创建一个名为 src 的文件夹,它将成为整个服务器中唯一的文件夹。

创建完名为 src 的文件夹之后,我们接着在 src 文件夹下创建3个文件。首先创建名为schema.graphql 的文件,在这个文件中,加入如下代码:

type Query {
 hello
: String
}

schema.graphql 文件将是整个 GraphQL 的架构,如果你不懂这是什么,别担心,我稍后会进行解释。

接着在 src 文件夹中新建第二个名为 resolvers.js 的文件,加入如下代码:

import { users } from "./db";
const resolvers = {
 
Query: {
   hello
: () => "Hello World!"
 
}
};
export default resolvers;

resolvers.js 文件是我们将 GraphQL 操作转换为数据的指令的方式。

最后,在 src 文件夹下创建第三个文件 db.js:

export let users = [
 
{ id: 1, name: "John Doe", email: "[email protected]", age: 22 },
 
{ id: 2, name: "Jane Doe", email: "[email protected]", age: 23 }
];

在本教程中,我们没有使用真实的数据库,因此,db.js 文件将模拟数据库,仅用于学习的目的。

现在 src 文件夹应该如下:

src
 
|--db.js
 
|--resolvers.js
 
|--schema.graphql

如果你在终端运行 npm run dev 或者 yarn dev,你就会在终端看到以下输出:

现在通过本地访问 localhost:4000,这意味着我们已经准备好开始在GraphQL中编写我们的第一个查询,变更和订阅。本地运行以后你可以看到 GraphQL Playground,这是一个功能强大的 GraphQL IDE,可用于更好的开发工作流程。如果你想了解更多关于 GraphQL Playground,请点击这里(https://www.prisma.io/blog/introducing-graphql-playground-f1e0a018f05d/)

概要

GraphQL 有自己的语言类型,用于编写模式。这是一种称为模式定义语言(SDL)的人类可读模式语法。无论你使用何种技术,SDL 都是相同的,你可以将其用于你想要的任何语言或框架。

这种模式语言非常有用,因为它很容易就知道你的 API 将具有哪些类型,这样你可以通过正确的方式来理解它。

类型(Types)

类型是 GraphQL 最重要的特性之一,类型是自定义的对象,代表着 API 的外观。举个例子:如果你正在构建一个社交媒体应用程序,那么你的 API 应该具有 Posts,Users,Likes,Groups 等类型。

类型具有字段,这些字段返回特定类型的数据。例如,我们创建一个用户类型,那么这个用户类型应该具有 name, email 和 age 字段。类型字段可以是任何类型,并始终返回一种数据类型,如 Int,Float,String,Boolean,ID,对象类型列表或自定义对象类型。

现在我们开始编写我们的第一个类型,在 schema.graphql 文件用如下代码替换已存在的Query 类型:

type User {
 id
: ID!
 name
: String!
 email
: String!
 age
: Int
}

每个用户都将拥有一个 ID,因此我们给它提供了 ID 类型。用户也会有一个 name 和eamil,所以我们给它一个 String 类型,而 age 我们给它一个Int类型,很简单吧?

那么,每行末尾的!是什么意思呢?感叹号表示字段不能为空,这意味着每个字段必须在每个查询中返回一些数据,用户 User 类型中唯一可以为空的字段是 age。

在 GraphQL 中,将处理三个主要概念:

  • 查询(queries):从服务器获取数据的方式。

  • 变更(mutations):修改服务器上的数据并获取更新的数据(创建,更新,删除)的方式。

  • 订阅(subscriptions):与服务器保持实时连接的方式。

接下来我将会向你一一解释这些概念,让我们先从查询开始吧!

查询(Queries)

简单的解释,GraphQL 中的查询就是你获取数据的方式。GraphQL 查询中最棒的事情之一就是可以获取到你所需的确切数据,不多也不少。这将对我们的 API 产生积极的影响——不再像使用 REST API 那样过度获取或提取不足的信息。

我们将在 GraphQL 中创建第一个类型的查询,我们所有的查询都将以此类型结束。首先,在 schema.graphql 编写一个名为 Query 的新类型:

type Query {
 users
: [User!]!
}

这很简单:users 查询将返回给我们一个或多个用户的数组,这个数组不会返回 null,因为我们给 users 加了!,这意味着 users 是一个不可为空的查询,它总是会返回数据。

但是我们也可以返回特定的用户,为此,我们将创建一个名为 user 的新查询。在我们的Query 类型中,写入以下代码:

user(id: ID!): User!

现在我们的 Query 类型应该如下所示:

type Query {
 users
: [User!]!
 user
(id: ID!): User!
}

如你所看到的,使用 GraphQL 中的查询,我们也可以传递参数。在这个例子中,我们要查询特定的用户,因此传递参数 ID。

但是,你可能会想:GraphQL 是怎么知道获取数据的具体位置的?这就是为什么我们还需要有一个 resolvers.js 文件,该文件告诉 GraphQL 它将如何以及从何处获取数据。

首先,在 resolvers.js 文件中导入刚刚创建的 db.js,这时你的 resolvers.js 文件应如下所示:

import { users } from "./db";
const resolvers = {
 
Query: {
   hello
: () => "Hello World!"
 
}
};
export default resolvers;

接着我们将创建第一个查询,找到 resolvers.js 文件并替换其中的 hello 函数。现在,你的查询类型应如下所示:

import { users } from "./db";
const resolvers = {
 
Query: {
   user
: (parent, { id }, context, info) => {
     
return users.find(user => user.id === id);
   
},
   users
: (parent, args, context, info) => {
     
return users;
   
}
 
}
};
export default resolvers;

现在,我将解释一下它是如何工作的:

每个查询解析器都有四个参数,在 user 函数中,我们将 id 作为参数传递,然后返回与传递的 id 匹配的特定用户,就是这么简单。而在 users 函数中,将返回已存在的 users 数组,它将始终给我们返回所有的用户。

我们将测试我们的查询是否运行正常,访问 localhost:4000 并输入以下代码:

query {
 users
{
   id    name    email    age  
}
}

这时候应该给我们返回所有的用户。

或者,如果要返回特定用户:

query {
 user
(id: 1) {
   id    name    email    age  
}
}

接下来,我们将开始学习变更(mutations),这是 GraphQL 中最重要的功能之一。

变更(Mutations)

在 GraphQL 中,mutations 是你修改服务器上的数据并获取更新数据的方式,你可以把它跟 REST 中的 CUD(创建,更新,删除)一样思考。

我们将在 GraphQL 中创建我们的第一个类型变更,我们所有的变更都将以这种类型结束。在 schema.graphql 文件中编写一个名为 mutation 的新类型:

type Mutation {
 createUser
(id: ID!, name: String!, email: String!, age: Int): User!
 updateUser
(id: ID!, name: String, email: String, age: Int): User!
 deleteUser
(id: ID!): User!
}

正如你所见,我们将有三个变更:

createUser:我们应该传递一个 id,name,email 和 age,它将会为我们创建一个新的用户。

updateUser:我们应该传递一个 id,一个新的 name,email 或 age,它将会为我们返回一个新用户。

deleteUser:我们应该传递一个 id,它将会为我们返回一个新用户。

现在,在 resolvers.js 文件中的 Query 对象下面,创建一个新的变更对象,如下所示:

Mutation: {
   createUser
: (parent, { id, name, email, age }, context, info) => {
     
const newUser = { id, name, email, age };
     users
.push(newUser);
     
return newUser;
   
},
   updateUser
: (parent, { id, name, email, age }, context, info) => {
     
let newUser = users.find(user => user.id === id);
     newUser
.name = name;
     newUser
.email = email;
     newUser
.age = age;
     
return newUser;
   
},
   deleteUser
: (parent, { id }, context, info) => {
     
const userIndex = users.findIndex(user => user.id === id);
     
if (userIndex === -1) throw new Error("User not found.");
     
const deletedUsers = users.splice(userIndex, 1);
     
return deletedUsers[0];
   
}
 
}

现在,resolvers.js 文件应如下所示:

import { users } from "./db";
const resolvers = {
 
Query: {
   user
: (parent, { id }, context, info) => {
     
return users.find(user => user.id === id);
   
},
   users
: (parent, args, context, info) => {
     
return users;
   
}
 
},
 
Mutation: {
   createUser
: (parent, { id, name, email, age }, context, info) => {
     
const newUser = { id, name, email, age };
     users
.push(newUser);
     
return newUser;
   
},
   updateUser
: (parent, { id, name, email, age }, context, info) => {
     
let newUser = users.find(user => user.id === id);
     newUser
.name = name;
     newUser
.email = email;
     newUser
.age = age;
     
return newUser;
   
},
   deleteUser
: (parent, { id }, context, info) => {
     
const userIndex = users.findIndex(user => user.id === id);
     
if (userIndex === -1) throw new Error("User not found.");
     
const deletedUsers = users.splice(userIndex, 1);
     
return deletedUsers[0];
   
}
 
}
};
export default resolvers;

接着,我们将测试我们的 mutations 是否正常工作,访问 localhost:4000 并输入以下代码:

mutation {
 createUser
(id: 3, name: "Robert", email: "[email protected]", age: 21) {
   id    name    email    age  
}
}

它将会返回给我们一个新的用户,如果你想尝试创建新的的 mutations,我建议你可以自己动手尝试一下!尝试删除你所创建的同一用户,看看它是否正常工作。

最后,我们将开始学习订阅(subscriptions),以及学习为什么它是如此的强大。

订阅(Subscriptions)
正如我之前所说,订阅(subscriptions)是你与服务器保持实时连接的方式。这意味着无论何时在服务器中发生事件,并且每当调用该事件时,服务器都会将相应的数据发送到客户端。

通过使用订阅,你可以保持你的应用在不同的用户之间得到最新的更改。

基本的订阅应该如下:

subscription {
 users
{
   id    name    email    age  
}
}

你可能会说它与查询非常相似,是的,但它们的工作方式不同。当服务器中的某些内容更新时,服务器将运行订阅中指定的GraphQL查询,并将新更新的结果发送到客户端。

总结

正如你所见,GraphQL 是一项非常强大的新技术。它为我们提供了构建更好的 API 的真正能力。这就是为什么我建议你现在就开始学习 GraphQL,对我来说,它最终将取代REST。

最后,为你推荐