什么是 GraphQL
GraphQL 是一种用于编写 API 查询的规范,也是一种用于对现有数据执行这些查询的运行时。它围绕 HTTP 协议构建,并定义了如何从服务器发送和接收资源。
使用 GraphQL,您不需要像创建 REST 端点时通常那样创建多个端点来请求数据。您将获得一个单一的端点,它会根据您发送到端点的查询返回所需的数据。
REST 与 GraphQL
假设您正在构建一个 CMS(一个内容管理系统)并且您的数据库中有两个表;user
和posts
。下面,您将看到 REST 和 GraphQL 如何处理您需要获取应用程序中每个用户的所有帖子的情况。
REST
使用 REST 时,您首先需要向第一个端点(例如/users
)发出请求,该端点查询user
模型以获取数据库中所有用户的列表。这样做之后,您可以提取id
每个用户的 ,然后再向第二个端点(例如 )发出另一个请求,该端点/users/posts
返回该用户创建的所有帖子。这意味着您必须对端点进行n
多次调用,即应用程序中的用户数。/users/posts``n
另请注意,来自/users
端点的响应可能包含其他用户信息,除了id
姓名、出生日期、位置、年龄等您在此上下文中并不真正需要的信息。这也适用于/users/posts
端点。这最终意味着您可以向端点发出更多您需要的请求,这可能会降低应用程序的速度。
GraphQL
使用 GraphQL,您只需要创建一个包含您需要的确切数据的查询。在这种情况下,您通过创建如下所示的查询告诉它您需要所有用户(仅他们的名字)和每个用户创建的所有帖子(仅标题)
query{ users{ name posts{ title } } } 复制代码
创建查询后,将其发送到 GraphQL 服务器,该服务器解析查询并返回查询中定义的适当数据。在这里,您可以从一个端点获得所需的一切,仅此而已。这在许多方面极大地改进了您的应用程序。
简而言之
简而言之,GraphQL 为我们提供了 REST API 可以做的一切,例如 CRUD 功能、错误处理等。然而,它通过减少发出的请求数量、基于简单有组织的查询嵌套数据来改进它,从而使整个服务器运行得更快并且更加优化。
Express + GraphQL
让我们制作一个方便的模板来开始使用 GraphQL 应用程序。您首先需要使用 GraphQL 设置一个简单的 Express 应用程序。然后向应用程序添加一些功能,这样您就可以看到 GraphQL 必须自己提供的一些功能。
首先,您可以通过运行以下命令创建一个基本的快速服务器;
% npm init --yes 复制代码
这会生成一个package.json
文件,其中包含有关您的应用程序的信息,以及其他信息,例如应用程序依赖项以及如何运行它。
您需要通过运行以下命令来安装您的应用程序依赖项,并将它们添加到您的package.json
文件中;
% npm i express express-graphql graphql 复制代码
这些依赖项允许您启动一个 express 服务器,将您的 express 服务器连接到 graphql ( express-graphql
) 并且还提供您需要的 GraphQL 功能 ( graphql
)。
您还将安装nodemon
以帮助在任何文件更改时重新启动服务器,dev
并向您的文件添加脚本package.json
。
% npm i --save-dev nodemon 复制代码
package.js
{ "scripts": { "dev": "nodemon index.js"} } 复制代码
PostgreSQL + docker
完成此操作后,您将使用 docker-compose 设置一个 postgreSQL 数据库,并用一些用户和帖子填充它,以便您可以测试您的应用程序。为此,您将创建一个docker-compose.yml
文件,其中包含您将使用的 postgres 服务的配置。您可以通过将以下行添加到 docker-compose 文件来进行设置
version: '3.8' services: postgres: image: postgres:13-alpine ports: - 5432:5432 env_file: - .env volumes: - postgres:/var/lib/postgresql/data networks: - graphql-express volumes: postgres: name: graphql-express-docker-db networks: graphql-express: 复制代码
然后继续到您的终端并运行docker compose up -d
或sudo docker-compose up -d
(对于 linux)。这将启动 postgres 服务并使其可用于您的应用程序。您还需要安装sequelize
,ORM
用于连接和查询您的数据库。为此,请安装以下内容
% npm install sequelize pg pg-hstore 复制代码
Sequelize 还提供了一个cli
通过提供 cli 命令使使用 ORM 更容易的工具。在此示例中,您将使用sequelize-cli
来设置数据库连接和架构。在终端中输入以下命令
% npm install --save-dev sequelize-cli ... % npx sequelize-cli init 复制代码
这将创建许多文件夹
config
,其中包含配置文件,它告诉 CLI 如何连接数据库models
, 包含您项目的所有模型migrations
, 包含所有迁移文件
创建这些之后,您需要创建用户和帖子模型。为此,您可以使用model:generate
带有模型名称和属性的命令
% npx sequelize-cli model:generate --name User --attributes name:string % npx sequelize-cli model:generate --name Post --attributes title:string 复制代码
这会生成用户和帖子模型,您可以编辑这些模型以添加关系。在模型中,您可以将以下行添加到文件user
中的静态associate
函数user.js
static associate({ Post }) { this.hasMany(Post, { foreignKey: "userId", onDelete: "CASCADE" }) } 复制代码
在post.js
文件中,您在模型中添加belongsTo
关联Post
static associate({ Post }) { this.hasMany(Post, { foreignKey: "userId", onDelete: "CASCADE" }) } 复制代码
db:migrate
然后,您可以运行迁移以使用以下命令将模式持久保存在数据库中
npx sequelize-cli db:migrate 复制代码
在此之后,您可以创建一个种子文件以默认插入一些原始数据。这将帮助您稍后测试服务器。您可以创建一个种子文件来创建演示用户。
% npx sequelize-cli seed:generate --name demo-user ... 复制代码
然后您可以继续将演示用户添加到种子文件。添加这些用户后,您可以使用db:seed:all
命令创建它们。
xxxxxxxxxxxxx-demo-user.js
'use strict'; /** @type {import('sequelize-cli').Migration} */ module.exports = { async up (queryInterface, Sequelize) { return queryInterface.bulkInsert('Users', [{ name: 'Naruto Uzumaki', createdAt: new Date(), updatedAt: new Date() }]); }, async down (queryInterface, Sequelize) { return queryInterface.bulkDelete('Users', null, {}); } }; 复制代码
然后
% npx sequelize-cli db:seed:all 复制代码
Express Server
index.js
然后,您可以通过将以下内容添加到文件来设置一个简单的服务器。这会启动服务器并添加GraphQL
到它。它使用带有graphqlHTTP
两个参数的函数schema
,这将在稍后定义,graphiql
并且提供用于与我们的服务器交互的 GUI。
const express = require('express'); const { graphqlHTTP } = require('express-graphql'); const { sequelize, User, Post} = require('./models'); const app = express(); app.use('/graphql', graphqlHTTP({ schema: schema, graphiql: true })) app.listen(5000, () => ( await sequelize.authenticate(); console.log("Running") )) 复制代码
不过,要使其正常工作,您需要定义一个模式,告诉 GraphQL 您的数据如何相互交互。该模式有两个参数;query
参数是您从服务器获取数据的一种方式,参数是为mutation
服务器提供其他CRUD
功能的参数。
const schema = new GraphQLSchema({ query: rootQuery, mutation: rootMutation }) 复制代码
要定义您的rootQuery
,请将以下行添加到您的服务器文件中。rootQuery
是GraphQLObjectType
定义对象的名称和描述的。它作为数据访问层 (DAL) 服务,定义对象字段,对象字段本身包含可用于从服务器获取数据的各种查询字段的定义。它还通过对数据库进行查询来解析这些字段并返回适当的数据。
// root query scope const rootQuery = new GraphQLObjectType({ name: "Query", description: "Root Query", fields: () => ({ posts: { type: new GraphQLList(PostType), description: 'List of posts', resolve: () => Post.findAll() }, users: { type: new GraphQLList(UserType), description: 'List of users', resolve: () => User.findAll() }, post: { type: PostType, description: 'A single post', args: { id: { type: GraphQLInt } }, resolve: (parent, args) => Post.findOne({ where: { id: args.id } }) }, user: { type: UserType, description: 'A single user', args: { id: { type: GraphQLInt } }, resolve: (parent, args) => User.findOne({ where: { id: args.id } }) } }) }) 复制代码
您还可以定义您的与对象rootMutation
非常相似的rootQuery
对象。但是它有所不同,因为它用于改变或更改服务器上的数据。
const rootMutation = new GraphQLObjectType({ name: "Mutation", description: "Root Mutation", fields: () => ({ addUser: { type: UserType, description: 'Add a user', args: { name: { type: GraphQLNonNull(GraphQLString) } }, resolve: (parent, args) => { const user = User.create({ name: args.name }) return user } }, addPost: { type: PostType, description: 'Add a post', args: { title: { type: GraphQLNonNull(GraphQLString) }, userId: { type: GraphQLNonNull(GraphQLInt) } }, resolve: (parent, args) => { const post = Post.create({ title: args.title, userId: args.userId }) return post } } }) }) 复制代码
therootQuery
和 the都rootMutation
定义了一些类型,例如 thePostType
和UserType
分别代表您的帖子和用户的定义。rootQuery
要定义这些与and有点相似的类型rootMutation
,您应该输入以下内容并根据需要进行编辑
const UserType = new GraphQLObjectType({ name: "User", description: "A User in our application", fields: () => ({ id: { type: GraphQLNonNull(GraphQLInt) }, name: { type: GraphQLNonNull(GraphQLString) }, posts: { type: new GraphQLList(PostType), resolve: (user) => Post.findAll({ where: { userId: user.id } }) } }) }) const PostType = new GraphQLObjectType({ name: "Post", description: "A Post created by a user", fields: () => ({ id: { type: GraphQLNonNull(GraphQLInt) }, title: { type: GraphQLNonNull(GraphQLString) }, userId: { type: GraphQLNonNull(GraphQLInt) }, user: { type: UserType, resolve: (post) => User.findOne({ where: { id: post.userId } }) } }) }) 复制代码
测试服务器
当你的数据库正在运行并且上面的一切都按照你的需要准备就绪时,你可以继续npm run dev
在你的终端会话中运行以启动服务器。您的 express 服务器应该可用,localhost:5000
并且您可以与提供良好用户界面的GraphQL
服务器进行交互。localhost:5000/graphql