使用Node.js和MongoDB构建的,学习GraphQL的过程

首先

由于我在维护和修改阶段加入了项目,所以没有做过基本的操作,如创建架构的工作。另外,我还没有使用过NoSQL数据库,所以我决定学习一下,尝试用Node.js+MongoDB来建立GraphQL服务器。

这次创建的项目已经上传到GitHub上了。

GraphQL的好处

GraphQL最大的特点在于它是一个单一的终端点,因此与REST进行比较的文章到处都有(例如, 这里)。

使用GraphQL可以解决在尝试使用REST获取所有需要的数据时可能遇到的以下问题。

    • 複数のエンドポイントへリクエストを行う必要がある

 

    不要なデータも一緒に取得されてしまう

服务器构建

创建一个适当的文件夹(graphql-server-practice)并运行yarn init。然后按照以下配置创建文件夹和文件。

スクリーンショット 2021-02-24 20.50.27.png

我们将在Schema.js中记录Schema、Query和Mutation。

创建模式

为了练习,我们将创建三个模式:User、Hobby和Post。
User与多个Hobby和Post之间形成了一对多的关系。

首先,通过运行”yarn add graphql”命令安装graphql包,然后在schema.js文件中加载以下内容。

const graphql = require('graphql');

const {
  GraphQLObjectType,
  GraphQLID,
  GraphQLString,
  GraphQLInt,
  GraphQLSchema,
  GraphQLNonNull,
  GraphQLList,
} = graphql;

用UserType作为User的模式创建。通过GraphQLObjectType进行封装,可以定义name、description和fields的模式。

在GraphQL中,我们将fields的id、name、age和profession定义为GraphQLID、GraphQLString、GraphQLInt和GraphQLString这些标量类型。

const UserType = new GraphQLObjectType({
  name: 'User',
  description: 'Documentation for user...',
  fields: () => ({
    id: { type: GraphQLID },
    name: { type: GraphQLString },
    age: { type: GraphQLInt },
    profession: { type: GraphQLString },
  }),
});

同样地,我们也将为HobbyType和PostType创建相应的选项。

const HobbyType = new GraphQLObjectType({
  name: 'Hobby',
  description: 'Hobby description',
  fields: () => ({
    id: { type: GraphQLID },
    title: { type: GraphQLString },
    description: { type: GraphQLString },
  }),
});

const PostType = new GraphQLObjectType({
  name: 'Post',
  description: 'Post description',
  fields: () => ({
    id: { type: GraphQLID },
    comment: { type: GraphQLString },
  }),
});

我已经创建了3个Schema,但是在目前的状态下,User和Post以及User和Hobby之间的关系还没有被定义,所以我们需要考虑这些关系。

让我们来思考一下用户和帖子的例子。

const UserType = new GraphQLObjectType({
  name: 'User',
  description: 'Documentation for user...',
  fields: () => ({
    id: { type: GraphQLID },
    name: { type: GraphQLString },
    age: { type: GraphQLInt },
    profession: { type: GraphQLString },
    posts: {
      type: new GraphQLList(PostType),
      resolve(parent, args) {
        return postsData.filter((data) => data.userId === parent.id);
      },
    },
  }),
});

const PostType = new GraphQLObjectType({
  name: 'Post',
  description: 'Post description',
  fields: () => ({
    id: { type: GraphQLID },
    comment: { type: GraphQLString },
    user: {
      type: UserType,
      resolve(parent, args) {
        return usersData.find((data) => data.id === parent.userId);
      },
    },
  }),
});

对于每个User,都存在多个Post。因此,在UserType的字段中,新创建的posts将成为PostType的数组类型,并被定义为new GraphQLList(PostType)。

    posts: {
      type: new GraphQLList(PostType),
      resolve(parent, args) {
        return postsData.filter((data) => data.userId === parent.id);
      },
    },

另外,resolve会定义要显示给哪个用户的帖子。parent指的是父级字段(在这里是UserType),在以上操作中它只会获取具有与用户id相等的userId的帖子数据。

const usersData = [
  { id: '1', name: '山田勝己', age: 36, profession: 'SASUKE' },
];

const postsData = [
  { id: '1', comment: '僕にはSASUKEしかないんです', userId: '1' },
  { id: '2', comment: '完全制覇がしたいんです', userId: '1' },
];

创建查询

在GraphQL中,查询和Schema一样,使用GraphQLObjectType进行定义。让我们试着创建一个user查询,用于获取指定id的用户,并且创建一个users查询,用于获取所有用户。

const RootQuery = new GraphQLObjectType({
  name: 'RootQueryType',
  description: 'Description',
  fields: {
    user: {
      type: UserType,
      args: { id: { type: GraphQLID } },

      resolve(parent, args) {
        return usersData.find((data) => data.id === args.id);
      },
    },

    users: {
      type: new GraphQLList(UserType),
      resolve(parent, args) {
        return usersData;
      },
    },
  },
});

用户查询中使用id作为参数(args),因此在fields中对args进行类型定义。在resolve中,编写了一个处理程序,仅获取与args的id相同的数据。

在users查询中,我们只需要获取所有数据,所以不需要args参数。

设置本地服务器并进行查询操作的确认。

在进行变异构建和与数据库的连接之前,您需要启动本地服务器以确认查询的操作。

将创建的 RootQuery 用 new GraphQLSchema 进行包装并导出。

module.exports = new GraphQLSchema({
  query: RootQuery
});

使用yarn命令添加express express-graphql所需的软件包,并进行以下设置。

const express = require('express');
const { graphqlHTTP } = require('express-graphql');

const schema = require('./schema/schema');

const app = express();

app.use(
  '/graphql',
  graphqlHTTP({
    graphiql: true,
    schema,
  })
);

app.listen(4000, () => {
  console.log('Listening for requests on my awesome port 4000');
});

通过在应用程序中使用 `node app` 命令,可以在4000端口上启动服务器。为了实时反映源代码的修改,我们将使用 `yarn global add nodemon` 命令来引入 nodemon。

运行nodemon app来启动服务器,打开http://localhost:4000/graphql,会出现以下画面。

スクリーンショット 2021-02-25 8.04.42.png
スクリーンショット 2021-02-25 8.10.45.png

与MongoDB的连接

与DB建立连接并实现Mutation操作。

MongoDB Atlas的设定

スクリーンショット 2021-02-25 21.26.37.png
スクリーンショット 2021-02-25 21.29.32.png
スクリーンショット 2021-02-25 21.31.25.png
スクリーンショット 2021-02-25 21.41.01.png
スクリーンショット 2021-02-25 21.43.06.png

Node.js 的配置

为了与GraphQL和MongoDB进行集成,我们将使用Node.js的mongoose包进行安装(yarn add mongoose)。
将app.js文件修改为以下内容。使用mongoose.connect连接到MongoDB,使用mongoose.connection.once在成功连接后进行控制台日志确认。

const express = require('express');
const { graphqlHTTP } = require('express-graphql');
const mongoose = require('mongoose');
const schema = require('./schema/schema');

const app = express();

mongoose.connect(
  'mongodb+srv://dbUser:<password>@cluster0.gjo5x.mongodb.net/test', // <password>には自分で設定したものを入力
  { useNewUrlParser: true }
);
mongoose.connection.once('open', () => {
  console.log('we are connected.');
});

app.use(
  '/graphql',
  graphqlHTTP({
    graphiql: true,
    schema,
  })
);

app.listen(4000, () => {
  console.log('Listening for requests on my awesome port 4000');
});

建立模型

スクリーンショット 2021-02-25 22.03.45.png

用户的模型将如下所示。userSchema将在稍后的schema.js文件中加载,最后进行导出。

const mongoose = require('mongoose');
const MSchema = mongoose.Schema;

const userSchema = new MSchema({
  name: String,
  age: Number,
  profession: String,
});
module.exports = mongoose.model('User', userSchema);

产生变异

我们将创建用户(User)数据的创建(CreateUser)、更新(UpdateUser)、删除(RemoveUser)的Mutation。

const graphql = require('graphql');

const User = require('../model/user');

~中略~

const Mutation = new GraphQLObjectType({
  name: 'Mutation',
  fields: {
    CreateUser: {
      type: UserType,
      args: {
        name: { type: new GraphQLNonNull(GraphQLString) },
        age: { type: new GraphQLNonNull(GraphQLInt) },
        profession: { type: GraphQLString },
      },

      resolve(parent, args) {
        let user = new User({
          name: args.name,
          age: args.age,
          profession: args.profession,
        });

        return user.save();
      },
    },

    UpdateUser: {
      type: UserType,
      args: {
        id: { type: new GraphQLNonNull(GraphQLString) },
        name: { type: new GraphQLNonNull(GraphQLString) },
        age: { type: GraphQLInt },
        profession: { type: GraphQLString },
      },

      resolve(parent, args) {
        return (updatedUser = User.findByIdAndUpdate(
          args.id,
          {
            $set: {
              name: args.name,
              age: args.age,
              profession: args.profession,
            },
          },
          { new: true }
        ));
      },
    },

    RemoveUser: {
      type: UserType,
      args: {
        id: { type: new GraphQLNonNull(GraphQLString) },
      },
      resolve(parent, args) {
        let removedUser = User.findByIdAndRemove(args.id).exec();

        if (!removedUser) {
          throw new 'Error'();
        }

        return removedUser;
      },
    },
}

~中略~

module.exports = new GraphQLSchema({
  query: RootQuery,
  mutation: Mutation,
});

在resolve中加载User模型,并使用save方法和findByIdAndUpdate方法进行数据的注册和更新。有关这些方法的详细使用说明,请参考mongoose的官方文档。

另外,在更新或删除操作中需指定所要处理的数据,因此args中的id必须为非空值。若要使某一列变为非空值,请使用GraphQLNonNull进行封装。

最后还要记得导出Mutation。

执行突变。

スクリーンショット 2021-02-25 22.47.16.png
スクリーンショット 2021-02-25 22.50.31.png

最后

这次的服务器建设工作花费了相当长的时间,但使用AWS AppSync只需一瞬间就建设完成了。这是相当令人震撼的体验,所以我打算在另一篇文章中写一下。此外,如果您对未提及的Post、Hobby的Model和Mutation等部分感兴趣的话,我建议您可以查看一下GitHub。

广告
将在 10 秒后关闭
bannerAds