敞开生长之旅!这是我参与「日新方案 2 月更文应战」的第 20 天,点击查看活动概况

什么是 GraphQL

GraphQL 是一种用于编写 API 查询的规范,也是一种用于对现有数据履行这些查询的运行时。它围绕 HTTP 协议构建,并界说了怎么从服务器发送和接收资源。

运用 GraphQL,您不需求像创立 REST 端点时一般那样创立多个端点来恳求数据。您将获得一个单一的端点,它会根据您发送到端点的查询返回所需的数据。

REST 与 GraphQL

假设您正在构建一个 CMS(一个内容管理体系)而且您的数据库中有两个表;userposts。下面,您将看到 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 -dsudo docker-compose up -d(对于 linux)。这将发动 postgres 服务并使其可用于您的应用程序。您还需求装置sequelizeORM用于衔接和查询您的数据库。为此,请装置以下内容

% 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,请将以下行增加到您的服务器文件中。rootQueryGraphQLObjectType界说目标的名称和描述的。它作为数据拜访层 (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界说了一些类型,例如 thePostTypeUserType分别代表您的帖子和用户的界说。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