使用 Deno 构建 GraphQL 服务器
GraphQL 是一种流行的 API 构建方法,因为它使开发人员可以轻松地仅访问他们需要的数据,从而节省带宽并减少瀑布式请求,最终带来更好的客户端性能。
在本教程中,我们将向您展示如何使用 Deno 构建 GraphQL API 服务器。
您还可以将 Apollo 与 npm 标识符 在本教程中 一起使用。但是请注意,npm
标识符在 Deno Deploy 上尚不可用。
Hello, World!(你好,世界!)
让我们使用 Deno 设置一个 “Hello, World!” 的 GraphQL 示例。
我们将创建一个名为 hello_world_server.ts
的空文件,并添加我们的依赖项。
import { Server } from "https://deno.land/std@0.166.0/http/server.ts";
import { GraphQLHTTP } from "https://deno.land/x/gql@1.1.2/mod.ts";
import { makeExecutableSchema } from "https://deno.land/x/graphql_tools@0.0.2/mod.ts";
import { gql } from "https://deno.land/x/graphql_tag@0.0.1/mod.ts";
我们将在下面的操作中看到这些依赖项,但简而言之:
Server
构建我们的 HTTP 服务器GraphQLHTTP
使用我们指定的模式创建 GraphQL HTTP 中间件makeExecutableSchema
基于我们的类型定义和解析器创建模式gql
创建我们的类型定义
首先,让我们使用 gql
设置类型和解析器
const typeDefs = gql`
type Query {
hello: String
}
`;
在这里,我们的 Query
类型有一个字段 “hello”,它是一个 String
。一旦我们有了类型,我们就需要为该 Query
提供一个解析器。解析器处理查询和变更的功能。
在本例中,我们希望 Query 所做的只是打印 “Hello, World!”
const resolvers = {
Query: {
hello: () => `Hello, World!`,
},
};
为了与我们的服务器一起工作,我们需要将我们的类型和解析器组合成一个模式。我们可以使用 makeExecutableSchema
来做到这一点
const schema = makeExecutableSchema({ resolvers, typeDefs });
然后,模式对象将传递给 GraphQLHTTP
,它位于我们的主 HTTP 服务器内部
const server = new Server({
handler: async (req) => {
const { pathname } = new URL(req.url);
return pathname === "/graphql"
? await GraphQLHTTP<Request>({
schema,
graphiql: true,
})(req)
: new Response("Not Found", { status: 404 });
},
port: 3000,
});
所以主要的包装器是一个新的 Server
对象。在其中,我们有一个 handler
来处理来自浏览器的请求。我们获取 url,如果它包含 /graphql
,我们运行 GraphQLHTTP
中间件,传入我们的模式并将 graphiql
设置为 true
(您也可以在没有 graphiql
的情况下执行此操作,使用 Postman/Insomnia 或直接使用 curl
)。
如果它不是 /graphql
页面,则返回 404 状态并打印 “Not found”。
最后一行将启动端口(我们分配的 3000 端口)上的服务器并监听
server.listenAndServe();
使用 --allow-net
标志运行此命令,以确保我们具有网络访问权限
$ deno run --allow-net hello_world_server.ts
如果我们然后转到 localhost:3000/graphql,我们将看到 graphiql 界面
然后您可以运行查询
query {
hello
}
并收到响应
{
"data": {
"hello": "Hello World!"
}
}
这样一来,您就可以使用 GraphQL 了!
Hello, (Prehistoric) World!(你好,(史前)世界!)
一切都很好,但是 GraphQL API 如此强大的原因在于与数据的交互。让我们扩展我们的示例,以便我们可以查询我们的数据,选择我们收到的响应,并将数据插入到我们的数据库中。
因此,让我们设置一个后端数据库,并加载一些我们可以查询和添加的数据。
对于我们的数据存储,我们将使用 Postgres(您可以通过按照我们的教程在此处设置 Postgres 实例 → 连接到 Postgres)。
接下来,让我们向我们的数据库添加一些极其重要的数据:dinosaurs.json。
除了添加一个 mutation(这是在 GraphQL 中添加数据的方式)之外,从我们的 “Hello, World!” 到使其工作几乎没有什么需要更改的。但是我们将重构我们的代码,以使我们的项目更整洁一些。
首先,我们将创建一个 typedefs.ts
文件,并将我们的类型移动到那里,以及 gql
导入
import { gql } from "https://deno.land/x/graphql_tag@0.0.1/mod.ts";
export const typeDefs = gql`
type Query {
allDinosaurs: [Dinosaur]
oneDinosaur(name: String): Dinosaur
}
type Dinosaur {
name: String
description: String
}
type Mutation {
addDinosaur(name: String, description: String): Dinosaur
}
`;
我们现在有更多类型需要定义
- 我们的两个查询是
allDinosaurs
和OneDinosaur
。allDinosaurs
返回一个列表(用方括号表示)。OneDinosaur
返回一个Dinosaur
。 Dinosaur
是我们从查询返回的对象,其中包含字符串名称和描述。addDinosaur
是我们的 Mutation,用于将具有名称和描述的Dinosaur
添加到我们的数据库。
我们还将把我们的 Query 和 Mutation 解析器移出到 resolvers.ts
,因为这些解析器现在包含更多功能。resolvers.ts
也是我们将连接到 Postgres 的地方。
因此,我们首先需要导入我们的 Postgres 库
import * as postgres from "https://deno.land/x/postgres@v0.14.2/mod.ts";
然后我们可以构建我们的连接到 Postgres 的函数
const connect = async () => {
// Get the connection string from the environment variable "DATABASE_URL"
const databaseUrl = Deno.env.get("DATABASE_URL")!;
// Create a database pool with three connections that are lazily established
const pool = new postgres.Pool(databaseUrl, 3, true);
// Connect to the database
const connection = await pool.connect();
return connection;
};
我们可以使用 Deno.env.get()
访问环境变量(我们将使用 --allow-env
标志来启用对 Postgres 环境变量的访问)。在这里,我们的 DATABASE_URL
是存储的 Postgres 连接字符串。然后,我们创建一个连接池并连接到数据库。
然后我们可以定义我们的 Query 和 Mutation 函数。我们将创建两个 Query 函数,一个用于获取数据库中所有恐龙的列表 (allDinosaurs
),另一个用于仅按名称获取一个恐龙 (oneDinosaur
)
const allDinosaurs = async () => {
const connection = await connect();
const result = await connection.queryObject`
SELECT name, description FROM dinosaurs
`;
return result.rows;
};
const oneDinosaur = async (args: any) => {
const connection = await connect();
const result = await connection.queryObject`
SELECT name, description FROM dinosaurs WHERE name = ${args.name}
`;
return result.rows;
};
allDinosaurs
连接到数据库并返回其中所有 Dinosaurs
的列表。 oneDinosaur
类似,但是
- 它接受一个参数,这将是我们想要的
Dinosaur
的名称 - 它使用该参数名称来查询数据库以查找该
Dinosaur
我们还有一个 Mutation 函数来将 Dinosaur
添加到数据库
const addDinosaur = async (args: any) => {
const connection = await connect();
const result = await connection.queryObject`
INSERT INTO dinosaurs(name, description) VALUES(${args.name}, ${args.description}) RETURNING name, description
`;
return result.rows[0];
};
一旦我们完成了所有函数,我们就可以在我们的解析器中引用它们
export const resolvers = {
Query: {
allDinosaurs: () => allDinosaurs(),
oneDinosaur: (_: any, args: any) => oneDinosaur(args),
},
Mutation: {
addDinosaur: (_: any, args: any) => addDinosaur(args),
},
};
将解析器和类型定义移出主文件(我们将重命名为 server.ts
)后,我们需要将它们导入到该文件中,但是 server.ts
的其余部分可以保持不变
import { Server } from "https://deno.land/std@0.166.0/http/server.ts";
import { GraphQLHTTP } from "https://deno.land/x/gql@1.1.2/mod.ts";
import { makeExecutableSchema } from "https://deno.land/x/graphql_tools@0.0.2/mod.ts";
import { resolvers } from "./resolvers.ts";
import { typeDefs } from "./typedefs.ts";
const schema = makeExecutableSchema({ resolvers, typeDefs });
const server = new Server({
handler: async (req) => {
const { pathname } = new URL(req.url);
return pathname === "/graphql"
? await GraphQLHTTP<Request>({
schema,
graphiql: true,
})(req)
: new Response("Not Found", { status: 404 });
},
port: 3000,
});
server.listenAndServe();
这次当我们运行 server.ts
时,我们还需要 --allow-env
标志,以使 Postgres 正常工作
deno run --allow-net --allow-env server.ts
如果我们转到 localhost:3000/graphql,我们将看到与之前类似的 graphiql 界面,但是这次,自动完成功能将具有我们新的 Query 和 Mutation 的选项,例如查询 allDinosaurs
query {
allDinosaurs {
name
description
}
}
这将生成数据库中所有恐龙的列表
请记住,GraphQL 的一个亮点是最终用户可以选择他们想要请求的数据。在本例中,我们可以选择仅检索 names
我们还可以运行 oneDinosaur
查询以仅获取一只恐龙
query {
oneDinosaur(name:"Aardonyx") {
name
description
}
}
最后,我们可以使用 addDinosaur
添加恐龙
mutation {
addDinosaur(name:"Deno",description:"the fastest Deno in the world") {
name
description
}
}
我们提供名称和描述,但我们也可以从 API 请求返回名称和描述,以检查是否已添加恐龙
为了更加确定,我们还可以再次使用 oneDinosaur
下一步是什么?
该服务器展示了使用 Deno 实现 GraphQL 的基本示例,但是您可以做更多的事情,例如定义更多类型、查询以及数据之间的关系。例如,在这里,每只恐龙都是独立的,但是我们可以向我们的定义条目添加一个 进化枝 类型,并展示 Aardonyx 与 Seitaad 和 Sefapanosaurus 的关系。
我们还可以构建一个前端来使用这些数据。要了解其外观,请查看我们的 如何构建具有完美 Lighthouse 分数的电子商务网站,以了解我们如何在 我们的周边商店 中使用 Shopify GraphQL API。