用Node创建GraphQL API:代码量少,性能高
在本教程中,我们将学习怎样用 Node.js 和 Express 搭建 GraphQL 服务器端 API。
读者可以从 GitHub repository 获取本例的最终源代码: https://github.com/JscramblerBlog/node-express-graphql-api
在构建 GraphQL API 前,我们先来介绍下 GraphQL,以及相对于使用 REST 方法创建 API 来说它所具有的优势。
GraphQL 是构建网络 API 的实时查询语言。它允许开发者对应用程序中使用的数据提供详尽的说明,这样可以在客户端精准查询全部所需数据。
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。打开一个新的 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 作为数据库。
创建工程并引入基本依赖包之后,现在来创建 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。
访问网址 : http://localhost:4000/graphql ,并运行下列 mutation query:
mutation {
createContact(firstName: "Jon", lastName: "Snow", email: "[email protected]") {
id,
firstName,
lastName,
email
}
}
使用下列 mutation,可以更新 id 为 1 的 contact:
mutation {
updateContact(id: 1, firstName: "Aegon", lastName: "Targaryen", email: "[email protected]")
}
也可以使用下列 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