使用 NodeJS 创建 GraphQL 服务器

2025-06-08

使用 NodeJS 创建 GraphQL 服务器

当涉及客户端和服务器应用程序之间的网络请求时,REST(表述性状态转移)是连接两者的最流行选择之一。在REST API的世界中,一切都围绕着将资源作为可访问的 URL 的理念展开。然后,我们使用 CRUD 操作(创建、读取、更新、删除),这些操作基本上是 HTTP 方法,例如 GET、POST、PUT 和 DELETE,来与数据交互。

以下是典型 REST 请求的示例:

// example of a request
https://swapi.co/api/people/

// response of the above request in JSON
{
  "results": [
  {
    "name": "Luke Skywalker",
    "gender": "male",
    "homeworld": "https://swapi.co/api/planets/1/",
    "films": [
    "https://swapi.co/api/films/2/",
    "https://swapi.co/api/films/6/",
    "https://swapi.co/api/films/3/",
    "https://swapi.co/api/films/1/",
    "https://swapi.co/api/films/7/"
    ],
  }
  {
    "name": "C-3PO",
    "gender": "n/a",
    "homeworld": "https://swapi.co/api/planets/1/",
    "films": [
    "https://swapi.co/api/films/2/",
    "https://swapi.co/api/films/5/",
    "https://swapi.co/api/films/4/",
    "https://swapi.co/api/films/6/",
    "https://swapi.co/api/films/3/",
    "https://swapi.co/api/films/1/"
    ],
  }
  ]
}
Enter fullscreen mode Exit fullscreen mode

REST API 的响应格式不一定是 JSON,但这是目前大多数 API 的首选方法。除了 REST 之外,还出现了另一种处理网络请求的方法:GraphQL。GraphQL 于 2015 年开源,它正在改变开发人员在服务器端编写 API 并在客户端处理 API 的方式。GraphQL由 Facebook 开发并积极维护。

REST 的缺点

GraphQL 是一种用于开发 API 的查询语言。与 REST(一种架构或“一种做事方式”)不同,GraphQL 的开发理念是客户端在单个请求中仅向服务器请求所需的一组数据。

在 REST 架构中,或者像我们上面的例子一样,当我们获取《星球大战》系列电影中卢克·天行者出演的电影信息时,我们会得到一个数组,films其中homeworld包含不同的 API URL,这些 URL 会将我们引导至不同的 JSON 数据集的详细信息。这无疑是过度获取的一个例子。客户端为了获取卢克·天行者出演的电影的详细信息以及他所属星球的名称,必须向服务器发送多个请求。

使用 GraphQL,可以将其解析为单个网络请求。跳转到 API url:https://graphql.github.io/swapi-graphql/并运行以下查询。

注意:在下面的示例中,您可以忽略 GraphQL API 背后的工作原理。在本教程的后面,我将逐步指导您构建您自己的(也许是第一个)GraphQL API。

{
    allPeople {
        edges {
            node {
                name
                gender
                homeworld {
                    name
                }
                filmConnection {
                    edges {
                        node {
                            title
                        }
                    }
                }
            }
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

我们将获取所需的数据,例如角色名称、他们的genderhomeworld以及他们出场的标题films。运行上述查询后,您将获得以下结果:

{
    "data": {
        "allPeople": {
            "edges": [
                {
                    "node": {
                        "name": "Luke Skywalker",
                        "gender": "male",
                        "homeworld": {
                            "name": "Tatooine"
                        },
                        "filmConnection": {
                            "edges": [
                                {
                                    "node": {
                                        "title": "A New Hope"
                                    }
                                },
                                {
                                    "node": {
                                        "title": "The Empire Strikes Back"
                                    }
                                },
                                {
                                    "node": {
                                        "title": "Return of the Jedi"
                                    }
                                },
                                {
                                    "node": {
                                        "title": "Revenge of the Sith"
                                    }
                                },
                                {
                                    "node": {
                                        "title": "The Force Awakens"
                                    }
                                }
                            ]
                        }
                    }
                },
                {
                    "node": {
                        "name": "C-3PO",
                        "gender": "n/a",
                        "homeworld": {
                            "name": "Tatooine"
                        },
                        "filmConnection": {
                            "edges": [
                                {
                                    "node": {
                                        "title": "A New Hope"
                                    }
                                },
                                {
                                    "node": {
                                        "title": "The Empire Strikes Back"
                                    }
                                },
                                {
                                    "node": {
                                        "title": "Return of the Jedi"
                                    }
                                },
                                {
                                    "node": {
                                        "title": "The Phantom Menace"
                                    }
                                },
                                {
                                    "node": {
                                        "title": "Attack of the Clones"
                                    }
                                },
                                {
                                    "node": {
                                        "title": "Revenge of the Sith"
                                    }
                                }
                            ]
                        }
                    }
                }
            ]
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

如果应用程序的客户端触发上述 GraphQL URL,它将只在网络上发送一个请求来获得所需的结果,从而消除了过度获取或发送多个请求的可能性。

先决条件

要遵循本教程,您只需要在本地机器上安装Nodejs并安装即可。npm

GraphQL 简介

简而言之,GraphQL是一种阐明如何请求数据的语法,通常用于从服务器到客户端检索数据(又名查询)或对其进行更改(又名变异)。

GraphQL 具有一些定义特征:

  • 它允许客户端精确指定所需的数据。这也称为声明式数据获取。
  • 它对网络层没有意见
  • 可以更轻松地组合来自多个来源的多组数据
  • 它在以模式和查询的形式声明数据结构时使用强类型系统。这有助于在发送网络请求之前验证查询。

GraphQL API 的构建块

GraphQL API 有四个构建块:

  • 模式
  • 询问
  • 突变
  • 解析器

Schema在服务器上以对象的形式定义。每个对象对应相应的数据类型,以便进行查询。例如:

type User {
    id: ID!
    name: String
    age: Int
}
Enter fullscreen mode Exit fullscreen mode

上面的架构定义了一个用户对象的形状,其中包含一个id用符号表示的必填字段!。此外,还包含其他字段,例如字符串name类型的和整数类型的 age 。这在查询数据时也用于验证架构。

查询是向 GraphQL API 发出请求的方式。例如,在上面的例子中,我们获取与《星球大战》角色相关的数据。让我们简化一下。在 GraphQL 中查询,就是请求对象上的特定字段。例如,使用与上面相同的 API,我们获取《星球大战》中所有角色的名称。您可以在下面看到它们的区别。图片左侧是查询,右侧是图片。

SS1

GraphQL 查询的优点在于,它们可以嵌套到任意深度。这在 REST API 中很难实现。操作会变得更加复杂。

下面是另一个嵌套查询的示例,更复杂一些。

SS2

突变 (Mutation):在 REST 架构中,修改数据的方式有两种:POST添加数据或PUT用数据更新现有字段。在 GraphQL 中,整体概念类似。您可以发送查询以在服务器端触发写入操作。然而,这种形式的查询被称为
突变 (Mutation)。

解析器是模式和数据之间的链接。它们提供可用于通过不同操作与数据库交互的功能。

在本教程中,您将学习如何使用我们刚刚学到的相同构建块通过Nodejs设置 GraphQL 服务器。

使用 GraphQL 实现 Hello World!

现在让我们编写我们的第一个 GraphQL 服务器。在本教程中,我们将使用Apollo 服务器。我们需要安装三个软件包,以便 Apollo 服务器能够作为中间件与我们现有的 Express 应用程序配合使用。Apollo 服务器的优点在于它可以与多个流行的 Node.js 框架一起使用:Express、KoaHapi。Apollo本身与库无关,因此可以在客户端和服务器应用程序中将其与许多第三方库连接。

打开终端并安装以下依赖项:

# First create a new empty directory
mkdir apollo-express-demo

# Then initialize it
npm init -y

# Install required dependencies
npm install --save graphql apollo-server-express express
Enter fullscreen mode Exit fullscreen mode

让我们简单了解一下这些依赖项的作用。

  • graphql是一个支持库,也是我们的目的所必需的模块。
  • apollo-server-express被添加到现有应用程序中,并且是相应的 HTTP 服务器支持包
  • expressNodejs 的 Web 框架

您可以查看下面的图片,其中我安装的所有依赖项都没有任何错误。

SS3

index.js使用以下代码在项目根目录下创建一个名为 的新文件

const express = require('express');
const { ApolloServer, gql } = require('apollo-server-express');

const typeDefs = gql`
    type Query {
        hello: String
    }
`;

const resolvers = {
    Query: {
        hello: () => 'Hello world!'
    }
};

const server = new ApolloServer({ typeDefs, resolvers });

const app = express();
server.applyMiddleware({ app });

app.listen({ port: 4000 }, () =>
    console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`)
Enter fullscreen mode Exit fullscreen mode

这是我们的初始服务器文件,我们首先需要该express模块。这gql是一个模板文字标签,用于将 GraphQL 模式编写为类型。该模式由类型定义组成,其中包含用于读取数据的强制查询类型。它还可以包含表示其他数据字段的字段和嵌套字段。在上面的示例中,我们定义了typeDefs编写 graphQL 模式。

接下来resolvers,我们来看一下。解析器用于从模式中返回字段的数据。我们在示例中定义了一个解析器,它将函数映射hello()到模式上实现。接下来,我们创建一个server使用该类ApolloServer来实例化并启动服务器的类。由于我们使用的是 Express,因此需要集成该类ApolloServer。我们将其作为方法传递applyMiddleware(),以app添加 Apollo 服务器的中间件。app这里代表现有应用程序,是 Express 的一个实例。

app.listen()最后,我们使用Express 模块本身提供的来引导服务器。要运行服务器,请打开终端并运行命令node index.js。现在,在浏览器窗口中访问 url:http://localhost:4000/graphql即可查看其运行情况。

Apollo Server 为您设置了 GraphQL Playground,以便您可以快速开始运行查询和探索模式,如下所示。

SS4

要运行查询,请在左侧可编辑空间中键入以下查询,然后按中间的▶(播放)按钮。

SS5

右侧的“Schema”选项卡描述了查询的数据类型hello。这直接来自typeDefs我们在服务器中定义的。

SS6

瞧!你刚刚创建了你的第一个 GraphQL 服务器。现在让我们将现有知识扩展到现实世界。

使用 GraphQL 构建 API

到目前为止,我们已经整理好了所有模块以及所有必要的术语。在本节中,我们将使用 Apollo Server 创建一个小型的星球大战 API,用于我们自己的演示。您现在可能已经猜到了,Apollo Server 是一个库,可以帮助您使用 Node.js 将 GraphQL 模式连接到 HTTP 服务器。它并不局限于特定的 Node 框架,例如,我们在上一节中使用了 ExpressJS。它也支持Koa、 Restify 、Hapi和 Lambda 。对于我们的 API,我们继续使用 Express。

使用 Babel 进行编译

如果您想从头开始,请继续。安装本节中的所有库Hello World! With GraphQL。以下是我们在上一节中安装的依赖项:

"dependencies": {
        "apollo-server-express": "^2.1.0",
        "express": "^4.16.4",
        "graphql": "^14.0.2"
    }
Enter fullscreen mode Exit fullscreen mode

我将使用相同的项目和相同的文件index.js来引导服务器。但在开始构建 API 之前,我想先向您展示如何在演示项目中使用 ES6 模块。使用 React 和 Angular 等前端库,它们已经支持 ES6 特性,例如importexport default语句。Node.js 版本8.x.x可以解决这个问题。我们只需要一个允许我们使用 ES6 特性编写 JavaScript 的转译器。您可以完全跳过这一步,直接使用老旧的require()语句。

那么,转译器是什么呢

转译器也称为“源到源编译器”,它从
用一种编程语言编写的源代码读取代码并用另一种语言生成等效的代码

就 Nodejs 而言,我们并非切换编程语言,而是需要使用我正在使用的 Node LTS 版本不支持的新语言特性。我将按照以下配置流程,设置Babel编译器并在我们的项目中启用它。

首先,您必须安装一些依赖项并进行-D标记,因为我们的开发环境只需要这些依赖项。

npm install -D babel-cli babel-preset-env babel-watch
Enter fullscreen mode Exit fullscreen mode

安装它们后,将.babelrc文件添加到项目的根目录并添加以下配置:

{
    "presets": [env]
}
Enter fullscreen mode Exit fullscreen mode

配置过程的最后一步是添加一个dev scriptin package.json。一旦发生更改,这将负责自动运行 babel 编译器。此外,它babel-watch还会负责重新启动Nodejs
Web 服务器。

"scripts": {
    "dev": "babel-watch index.js"
}
Enter fullscreen mode Exit fullscreen mode

要查看它的操作,请将以下代码添加到您的代码中index.js,看看一切是否正常运行。

import express from "express"

const app = express()

app.get("/", (req, res) => res.send("Babel Working!"))

app.listen({ port: 4000 }, () => console.log(`🚀 Server ready at http://localhost:4000`))
Enter fullscreen mode Exit fullscreen mode

从终端写入npm run dev。如果没有错误,您将获得以下内容:

SS7

您还可以http://localhost:4000/在浏览器窗口中访问以查看其操作。

添加架构

api/schema.js我们需要一个模式来启动我们的 GraphQL API。让我们在目录中创建一个名为 的新文件api。添加以下模式。

我们的模式总共包含两个查询。第一个查询allPeople用于获取并列出 API 中的所有角色。第二个查询person用于使用 ID 检索一个人。这两种查询类型都依赖于一个名为Personobject 的自定义类型,该类型包含四个属性。

import { gql } from "apollo-server-express"

const typeDefs = gql`
    type Person {
        id: Int
        name: String
        gender: String
        homeworld: String
    }
    type Query {
        allPeople: [Person]
        person(id: Int!): Person
    }
`

export default typeDefs
Enter fullscreen mode Exit fullscreen mode

添加解析器

我们已经了解了解析器的重要性。它基于一种简单的机制,即链接模式和数据。解析器是包含查询或修改背后逻辑的函数。它们用于检索数据并在相关请求中返回。

如果您在使用 Express 之前构建过服务器,则可以将解析器视为控制器,其中每个控制器都是针对特定路由构建的。由于我们在服务器后端没有使用任何数据库,因此必须提供一些虚拟数据来模拟我们的 API。

创建一个名为的新文件resolvers.js并添加以下代码。

const defaultData = [
    {
        id: 1,
        name: "Luke SkyWaler",
        gender: "male",
        homeworld: "Tattoine"
    },
    {
        id: 2,
        name: "C-3PO",
        gender: "bot",
        homeworld: "Tattoine"
    }
]

const resolvers = {
    Query: {
        allPeople: () => {
            return defaultData
        },
        person: (root, { id }) => {
            return defaultData.filter(character => {
                return (character.id = id)
            })[0]
        }
    }
}

export default resolvers
Enter fullscreen mode Exit fullscreen mode

首先,我们定义一个defaultData数组,其中包含《星球大战》中两个角色的详细信息。根据我们的模式,数组中的这两个对象都具有四个属性。接下来是一个resolvers包含两个函数的对象。allPeople()稍后可以使用这两个函数来检索数组中的所有数据defaultDataperson()箭头函数使用一个参数id来检索具有请求 ID 的人员对象。我们已经在查询中定义了这个函数。

您必须导出解析器和模式对象才能将它们与 Apollo Server 中间件一起使用。

实现服务器

现在我们已经定义了 schema 和解析器,我们将在文件中实现服务器index.js。首先从 导入 Apollo Server apollo-server-express。我们还需要从 文件夹导入 schema 和解析器对象api/。然后,使用 Apollo Server Express 库中的 GraphQL 中间件实例化 GraphQL API。

import express from "express"
import { ApolloServer } from "apollo-server-express"

import typeDefs from "./api/schema"
import resolvers from "./api/resolvers"

const app = express()

const PORT = 4000

const SERVER = new ApolloServer({
    typeDefs,
    resolvers
})

SERVER.applyMiddleware({ app })

app.listen(PORT, () => console.log(`🚀 GraphQL playground is running at http://localhost:4000`))
Enter fullscreen mode Exit fullscreen mode

最后,我们使用 引导 Express 服务器app.listen()。现在您可以从终端执行以下命令来运行服务器npm run dev。Node 服务器启动后,它将提示一条成功消息,表明服务器已启动。

现在测试我们的 GraphQL API,跳转到浏览器窗口http://localhost:4000/graphql中的 URL
并运行以下查询。

{
    allPeople {
        id
        name
        gender
        homeworld
    }
}
Enter fullscreen mode Exit fullscreen mode

点击播放按钮,您将在右侧部分看到如下所示的熟悉的结果。

SS8

这一切发生是因为我们的查询类型allPeople具有自定义业务逻辑,需要使用解析器检索所有数据(在本例中,我们以数组形式提供的模拟数据resolvers.js)。要获取单个 Person 对象,请尝试运行另一个类似的查询。记住,您必须提供 ID。

{
    person(id: 1) {
        name
        homeworld
    }
}
Enter fullscreen mode Exit fullscreen mode

运行上述查询,即可获得所提及的每个字段/属性的值。结果将类似于以下内容。

SS9

太棒了!我相信你一定已经掌握了如何创建并运行 GraphQL 查询。Apollo Server 库功能强大,它还能让我们编辑 Playground。假设我们想编辑 Playground 的主题?我们只需在创建ApolloServer实例时提供一个选项即可,在本例中是SERVER

const SERVER = new ApolloServer({
    typeDefs,
    resolvers,
    playground: {
        settings: {
            "editor.theme": "light"
        }
    }
})
Enter fullscreen mode Exit fullscreen mode

playground属性具有许多功能,例如定义 Playground 的默认端点以及更改主题。您甚至可以在生产模式下启用 Playground。更多可配置选项可在 Apollo Server 的官方文档中找到

更改主题后,我们得到以下内容。

SS10

结论

如果你一步一步完成了本教程,恭喜你!🎉

您已经学习了如何使用 Apollo 库配置 Express 服务器来设置您自己的 GraphQL API。Apollo Server 是一个开源项目,也是为全栈应用程序创建 GraphQL API 的最稳定解决方案之一。它还支持 React、Vue、Angular、Meteor 和 Ember 的开箱即用客户端,以及使用 Swift 和 Java 的原生移动开发。更多信息,请点击此处

本教程的完整代码位于此 Github 存储库 👇

https://github.com/amandeepmittal/apollo-express-demo

这篇文章最初发布于此处



我经常撰写有关 Web 技术和 React Native 的文章。你可以在Twitter上关注我,也可以订阅我的每周新闻邮件,这样就能直接在邮箱里收到我的所有教程了📧

鏂囩珷鏉ユ簮锛�https://dev.to/amanhimself/creating-a-graphql-server-with-nodejs-17a3
PREV
在 React Native 中使用样式化组件
NEXT
10 个著名的机器学习 JavaScript 库