用Node创建GraphQL API:代码量少,性能高

2019 年 5 月 31 日 前端之巅

作者 | Ahmed Bouchefra
译者 | 方彦

在本教程中,我们将学习怎样用 Node.js 和 Express 搭建 GraphQL 服务器端 API。

读者可以从 GitHub repository 获取本例的最终源代码: https://github.com/JscramblerBlog/node-express-graphql-api

在构建 GraphQL API 前,我们先来介绍下 GraphQL,以及相对于使用 REST 方法创建 API 来说它所具有的优势。

什么是 GraphQL

GraphQL 是构建网络 API 的实时查询语言。它允许开发者对应用程序中使用的数据提供详尽的说明,这样可以在客户端精准查询全部所需数据。

GraphQL 的优势

Facebook,IBM,GitHub 和 Twitter 等大公司已从 REST 转到了 GraphQL API,因为 GraphQL 有很多优点,简单概括如下:

  • GraphQL 让开发人员可以只发送一条请求来获取数据,而如果用 REST 则可能需要多个 endpoint。这意味着开发人员可以少写代码,提高生产率。据 PayPal Engineering team(https://medium.com/paypal-engineering/graphql-a-success-story-for-paypal-checkout-3482f724fb53)的数据,UI 开发人员在实现 UI 时能节省至少 1/3 的时间。

  • GraphQL 让开发人员可以在单个查询中描述所需的数据、发送 / 接收更少的网络请求和响应、减少网络开销,从而提供更好的性能。

  • 支持嵌套数据,发送复杂查询请求比 REST 简单。

前提条件

如果想要从头学习本教程,需要以下几点:

  • 了解 JavaScript,熟悉 Node。

  • 已在开发机器上安装了 Node 和 NPM。可以直接到官方网站下载系统所需的二进制代码或使用 NVM 来安装 Node.js: https://nodejs.org/en/download/

如果上面两条都满足,就可以开始了。

创建 Project

我们现在来创建我们的 project。打开一个新的 terminal,执行以下命令,即可使用缺省值创建 package.json 文件:

mkdir node-graphql-demo 
cd node-graphql-demo
npm init -y

接下来,我们需要安装以下依赖:

npm install graphql express express-graphql sqlite3 --save

这样就会安装 Express 框架,Node.js 上的 GraphQL 实现,Express 和 SQLite3 的 GraphQL 中间件。为简单起见,我们使用 SQLite3 作为数据库。

创建 GraphQL 服务器

创建工程并引入基本依赖包之后,现在来创建 API 服务器。在工程文件夹里,创建 index.js 文件,并引入下列内容:

const express = require('express'); 
const sqlite3 = require('sqlite3').verbose();
const graphql = require("graphql");
const ExpressGraphQL = require("express-graphql");

上面的代码的目的是:为Express导入Express,SQLite3,GraphQL和GraphQL中间件。

接下来,添加下列代码,在当前文件夹中创建一个 Express 应用程序和名为 my.db 的 SQLite 3 数据库:

const app = express(); 
const database = new sqlite3.Database("./my.db");

然后,添加 createContactTable() 方法,在数据库中创建 contacts 表并马上调用函数:

const createContactTable = () => {
const query = `
CREATE TABLE IF NOT EXISTS contacts (
id integer PRIMARY KEY,
firstName text,
lastName text,
email text UNIQUE)`;
return database.run(query);
}
createContactTable();

我们创建了一个 SQL 表来存储 contacts的基本信息。每个 contact 的基本信息包括:唯一的标识、名、姓和 email。

接下来,添加下列代码来定义一个 GraphQL 类型:

const ContactType = new graphql.GraphQLObjectType({
name: "Contact",
fields: {
id: { type: graphql.GraphQLID },
firstName: { type: graphql.GraphQLString },
lastName: { type: graphql.GraphQLString },
email: { type: graphql.GraphQLString }
}
});

我们使用基本的内置 GraphQL 类型,如 GraphQLID 和 GraphQLString 来创建我们自定义类型,对应数据库中的 contact。

相关链接:

GraphQLID: https://graphql.github.io/graphql-spec/draft/#sec-ID

GraphQLString: https://graphql.github.io/graphql-spec/draft/#sec-String

接着,定义查询类型,如下所示:

var queryType = new graphql.GraphQLObjectType({  
name: 'Query',
fields: {
contacts: {
type: graphql.GraphQLList(ContactType),
resolve: (root, args, context, info) => {
return new Promise((resolve, reject) => {
database.all("SELECT * FROM contacts;", function (err, rows) {
if (err) {
reject([]);
}
resolve(rows);
});
});

}
},
contact: {
type: ContactType,
args: {
id: {
type: new graphql.GraphQLNonNull(graphql.GraphQLID)
}
},
resolve: (root, {
id
}, context, info) => {
return new Promise((resolve, reject) => {

database.all("SELECT * FROM contacts WHERE id = (?);", [id], function (err, rows) {
if (err) {
reject(null);
}
resolve(rows[0]);
});
});
}
}
}
});

我们的查询有两个字段: contacts,可以用来获取数据库中的所有 contacts,而 contact 则根据 id 获取一个 contact 信息。 contact 字段允许所需的 id 参数为 GraphQLID 类型。

每个字段都有一个 type,用来说明返回数据的类型,args 定义期望从客户端得到的参数, resolve 则定义了在获取数据逻辑中实际使用的方法。

对于前两个字段, resolve() 方法是实际逻辑发生的地方—— 我们简单调用 database.all() 和 database.run() 方法来执行正确的 SQL 查询,以便从 SQLite 获取数据,返回一个 Promise 来处理得到的数据。

我们可以从resolve()方法的第二个参数访问任何传递的参数。

接下来,我们创建一个 mutation 类型,用于创建、更新和删除操作: https://graphql.github.io/graphql-spec/draft/#sec-Mutation

var mutationType = new graphql.GraphQLObjectType({  
name: 'Mutation',
fields: {
createContact: {
type: ContactType,
args: {
firstName: {
type: new graphql.GraphQLNonNull(graphql.GraphQLString)
},
lastName: {
type: new graphql.GraphQLNonNull(graphql.GraphQLString)
},
email: {
type: new graphql.GraphQLNonNull(graphql.GraphQLString)
}
},
resolve: (root, {
firstName,
lastName,
email
}) => {
return new Promise((resolve, reject) => {
database.run('INSERT INTO contacts (firstName, lastName, email) VALUES (?,?,?);', [firstName, lastName, email], (err) => {
if (err) {
reject(null);
}
database.get("SELECT last_insert_rowid() as id", (err, row) => {

resolve({
id: row["id"],
firstName: firstName,
lastName: lastName,
email: email
});
});
});
})

}
},
updateContact: {
type: graphql.GraphQLString,
args: {
id: {
type: new graphql.GraphQLNonNull(graphql.GraphQLID)
},
firstName: {
type: new graphql.GraphQLNonNull(graphql.GraphQLString)
},
lastName: {
type: new graphql.GraphQLNonNull(graphql.GraphQLString)
},
email: {
type: new graphql.GraphQLNonNull(graphql.GraphQLString)
}
},
resolve: (root, {
id,
firstName,
lastName,
email
}) => {
return new Promise((resolve, reject) => {
database.run('UPDATE contacts SET firstName = (?), lastName = (?), email = (?) WHERE id = (?);', [firstName, lastName, email, id], (err) => {
if (err) {
reject(err);
}
resolve(`Contact #${id} updated`);

});
})
}
},
deleteContact: {
type: graphql.GraphQLString,
args: {
id: {
type: new graphql.GraphQLNonNull(graphql.GraphQLID)
}
},
resolve: (root, {
id
}) => {
return new Promise((resolve, reject) => {
database.run('DELETE from contacts WHERE id =(?);', [id], (err) => {
if (err) {
reject(err);
}
resolve(`Contact #${id} deleted`);

});
})

}
}
}
});

我们的 mutation 类型有三个字段:

  • createContact 创建 contacts;

  • updateContact 更新 contacts;

  • deleteContact 删除 contacts.

所有的字段都接受符合 args 属性定义的参数,并由一个 resolve() 方法来获取传递过来的参数,执行相应的 SQL 操作,并返回一个 Promise。

然后,创建 GraphQL schema: https://graphql.github.io/graphql-spec/draft/#sec-Schema

const schema = new graphql.GraphQLSchema({  
query: queryType,
mutation: mutationType
});

GraphQL schema 是 GraphQL 的核心概念,它定义了连接到服务器的客户端可用的功能。我们传递已定义的 query 和 mutation 类型到 schema。

最后,挂载到 /graphql 端点,在 4000 端口运行 Express 服务器:

app.use("/graphql", ExpressGraphQL({ schema: schema, graphiql: true}));  
app.listen(4000, () => {
console.log("GraphQL server running at http://localhost:4000.");
});

保存 index.js 文件,返回到你的 terminal。运行下列命令,启动服务器:

node index.js 
怎样使用 GraphQL API

构建客户端前,可以使用 GraphQL 接口来测试你的 API。

访问网址 : http://localhost:4000/graphql ,并运行下列 mutation query:

mutation {  
createContact(firstName: "Jon", lastName: "Snow", email: "jonsnow@thenightswatch.com") {
id,
firstName,
lastName,
email
}
}

使用下列 mutation,可以更新 id 为 1 的 contact:

mutation {  
updateContact(id: 1, firstName: "Aegon", lastName: "Targaryen", email: "aegontargaryen@ironthrone.com")
}

也可以使用下列 mutation 来删除 id 为 1 的 contact:

mutation {  
deleteContact(id: 1)
}

最后,使用如下 query,可以获取数据库中所有 contacts 信息:

query {  
contacts {
id
firstName
lastName
email
}
}

使用下面 query,可以获取一个 contact 的信息:

query {  
contact(id: 1) {
id
firstName
lastName
email
}
}

这里,我们得到的是 id 为 1 的 contact 信息。

结论

在本教程中,我们使用了 Node 和 Express.js 来创建一个简单的 GraphQL API,用以从 SQLite 数据库中读取、创建、更新和删除 contacts。

我们看到,GraphQL 是实时的查询语言,相对于使用 REST 方法来创建网络 APIs 而言,有很多的优点。我们也看到了怎样使用 GraphQL 的基本内置类型,并用它们来创建我们自己的 query 和 mutation 类型来读取和转换数据。

最后,我们学习了怎样使用 GraphQL 接口,通过我们的 API 来执行 query 和 mutation。

英文原文: https://blog.jscrambler.com/build-a-graphql-api-with-node/?utm_source=echojs.com&utm_medium=referral

作者博客: https://blog.jscrambler.com/author/ahmed

登录查看更多
2

相关内容

sqlite 的第三个主要版本
【2020新书】实战R语言4,323页pdf
专知会员服务
100+阅读 · 2020年7月1日
【实用书】学习用Python编写代码进行数据分析,103页pdf
专知会员服务
192+阅读 · 2020年6月29日
【实用书】Python技术手册,第三版767页pdf
专知会员服务
234+阅读 · 2020年5月21日
【实用书】Python爬虫Web抓取数据,第二版,306页pdf
专知会员服务
117+阅读 · 2020年5月10日
TensorFlow Lite指南实战《TensorFlow Lite A primer》,附48页PPT
专知会员服务
69+阅读 · 2020年1月17日
【干货】大数据入门指南:Hadoop、Hive、Spark、 Storm等
专知会员服务
95+阅读 · 2019年12月4日
5大必知的图算法,附Python代码实现
AI100
4+阅读 · 2019年9月10日
用Now轻松部署无服务器Node应用程序
前端之巅
16+阅读 · 2019年6月19日
专访阿里亚顿:Serverless与BFF与前端
前端之巅
45+阅读 · 2019年5月8日
PHP使用Redis实现订阅发布与批量发送短信
安全优佳
7+阅读 · 2019年5月5日
VS Code Remote发布!真·远程开发
开源中国
6+阅读 · 2019年5月3日
使用 C# 和 Blazor 进行全栈开发
DotNet
6+阅读 · 2019年4月15日
如何用GitLab本地私有化部署代码库?
Python程序员
9+阅读 · 2018年12月29日
R工程化—Rest API 之plumber包
R语言中文社区
11+阅读 · 2018年12月25日
Arxiv
10+阅读 · 2020年4月5日
Heterogeneous Graph Transformer
Arxiv
27+阅读 · 2020年3月3日
Embedding Logical Queries on Knowledge Graphs
Arxiv
3+阅读 · 2019年2月19日
dynnode2vec: Scalable Dynamic Network Embedding
Arxiv
14+阅读 · 2018年12月6日
Rapid Customization for Event Extraction
Arxiv
7+阅读 · 2018年9月20日
Arxiv
5+阅读 · 2018年6月5日
Arxiv
10+阅读 · 2018年2月4日
VIP会员
相关资讯
5大必知的图算法,附Python代码实现
AI100
4+阅读 · 2019年9月10日
用Now轻松部署无服务器Node应用程序
前端之巅
16+阅读 · 2019年6月19日
专访阿里亚顿:Serverless与BFF与前端
前端之巅
45+阅读 · 2019年5月8日
PHP使用Redis实现订阅发布与批量发送短信
安全优佳
7+阅读 · 2019年5月5日
VS Code Remote发布!真·远程开发
开源中国
6+阅读 · 2019年5月3日
使用 C# 和 Blazor 进行全栈开发
DotNet
6+阅读 · 2019年4月15日
如何用GitLab本地私有化部署代码库?
Python程序员
9+阅读 · 2018年12月29日
R工程化—Rest API 之plumber包
R语言中文社区
11+阅读 · 2018年12月25日
相关论文
Arxiv
10+阅读 · 2020年4月5日
Heterogeneous Graph Transformer
Arxiv
27+阅读 · 2020年3月3日
Embedding Logical Queries on Knowledge Graphs
Arxiv
3+阅读 · 2019年2月19日
dynnode2vec: Scalable Dynamic Network Embedding
Arxiv
14+阅读 · 2018年12月6日
Rapid Customization for Event Extraction
Arxiv
7+阅读 · 2018年9月20日
Arxiv
5+阅读 · 2018年6月5日
Arxiv
10+阅读 · 2018年2月4日
Top
微信扫码咨询专知VIP会员