跳到主要内容
How to build a GraphQL Server with Deno

使用 Deno 构建 GraphQL 服务器

GraphQL 是一种流行的 API 构建方法,因为它使开发人员可以轻松地仅访问他们需要的数据,从而节省带宽并减少瀑布式请求,最终带来更好的客户端性能。

在本教程中,我们将向您展示如何使用 Deno 构建 GraphQL API 服务器。

在此处查看源代码.

您还可以将 Apollonpm 标识符 在本教程中 一起使用。但是请注意,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 界面

Demo of the graphiql interface

然后您可以运行查询

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
  }
`;

我们现在有更多类型需要定义

  • 我们的两个查询是 allDinosaursOneDinosaurallDinosaurs 返回一个列表(用方括号表示)。 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 类似,但是

  1. 它接受一个参数,这将是我们想要的 Dinosaur 的名称
  2. 它使用该参数名称来查询数据库以查找该 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
  }
}

这将生成数据库中所有恐龙的列表

Query all dinosaurs.

请记住,GraphQL 的一个亮点是最终用户可以选择他们想要请求的数据。在本例中,我们可以选择仅检索 names

Query all dinosaurs, but only get the names.

我们还可以运行 oneDinosaur 查询以仅获取一只恐龙

query {
  oneDinosaur(name:"Aardonyx") {
    name
    description
  }
}

Query a single dinosaur named Aardonyx

最后,我们可以使用 addDinosaur 添加恐龙

mutation {
  addDinosaur(name:"Deno",description:"the fastest Deno in the world") {
    name
    description
  }
}

我们提供名称和描述,但我们也可以从 API 请求返回名称和描述,以检查是否已添加恐龙

Add Deno to database via mutation.

为了更加确定,我们还可以再次使用 oneDinosaur

Query one dinosaur, Deno.

下一步是什么?

该服务器展示了使用 Deno 实现 GraphQL 的基本示例,但是您可以做更多的事情,例如定义更多类型、查询以及数据之间的关系。例如,在这里,每只恐龙都是独立的,但是我们可以向我们的定义条目添加一个 进化枝 类型,并展示 Aardonyx 与 Seitaad 和 Sefapanosaurus 的关系。

我们还可以构建一个前端来使用这些数据。要了解其外观,请查看我们的 如何构建具有完美 Lighthouse 分数的电子商务网站,以了解我们如何在 我们的周边商店 中使用 Shopify GraphQL API。

请在 Twitter 或我们的 Discord 上告诉我们您还想学习使用 Deno 做什么。