发布于 2026-01-06 2 阅读
0

使用 GraphQL、Node.js 和 Sequelize 构建 API DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

使用 GraphQL、Node.js 和 Sequelize 构建 API

由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!

什么是GraphQL?

与传统的 RESTful 架构不同,在 RESTful 架构中,端点会返回 API 指定的一组数据,无论你是否需要;而 GraphQL 允许你从 API 提供的数据中挑选你想要的确切数据。

假设您想访问仓库中的某些商品,有两种方法可以实现:方法 A方法 B。两种方法都需要正确的访问密钥才能访问仓库中的商品。使用方法 A,您会收到仓库中所有商品的副本,需要从中选择您实际需要的商品。使用方法 B,您可以指定所需的商品,并且只会收到这些商品,无需获取整个仓库的副本。方法 A采用 RESTful 架构,而方法 B采用 GraphQL。

请访问GraphQL 网站了解更多关于 GraphQL 的信息

本文将重点介绍如何使用 GraphQL、Node.js、Sequelize 和 PostgreSQL 构建 API。我们将构建一个简单的博客 API,包含用户、文章和评论功能。用户应该能够进行身份验证(注册、登录、创建文章、查看文章、发表评论和查看评论)。

目录

  • 术语解释

    • 模式和类型
    • 解析器
    • 查询和变异
    • 语境
  • 设置项目并安装依赖项

  • 使用 Sequelize 创建数据库迁移和模型

  • 为用户、帖子和评论创建 GraphQL 模式

    • 用户模式
    • 帖子架构
    • 评论模式
  • 创建解析器

    • 用户解析器
      • 请求身份验证
    • 帖子解析器
    • 评论解析器
  • 结论

  • 更多资源

  • 接下来呢?

术语解释

  • 模式和类型模式定义了可查询数据的结构,而类型定义了数据的格式,例如我们已知的数据类型。点击此处了解更多关于模式和类型的信息。
  • 解析器:GraphQL 服务器上的一个函数,负责获取单个字段或整个模式的数据。

  • 查询和变更:这些是特殊的 GraphQL 类型。查询代表 REST API 中的 GET 请求,而变更代表 REST API 中的 POST、PUT 和 DELETE 请求。了解更多

  • 上下文:上下文是 GraphQL 中的一个全局对象。上下文中可用的数据在所有解析器之间共享。

请记住这些解释,我们准备开始编写代码。

有很多库实现了 GraphQL,在本文中,我将使用Apollo GraphQL

要跟随本文操作,请在此处克隆本文中使用的代码仓库。

设置项目并安装依赖项

  • 打开终端,为项目创建一个文件夹。


$ mkdir graphql-node-sequelize && cd graphql-node-sequelize

$ npm init -y


Enter fullscreen mode Exit fullscreen mode
  • 安装依赖项


$ npm install express graphql apollo-server-express bcryptjs core jsonwebtoken pg pg-hstore sequelize dotenv

$ npm install -D nodemon


Enter fullscreen mode Exit fullscreen mode
  • 设置服务器


$ mkdir api graphql 

$ cd graphql && mkdir resolvers schemas context


Enter fullscreen mode Exit fullscreen mode

我们为服务器创建了一个名为 的文件夹api,并为住房创建了另一个名为 graphql 的文件夹resolversschemas以及context

请注意,这种结构完全是我个人的意见,并不代表任何标准,您可以根据自己的喜好来构建项目。

  • index.js在 schemas 文件夹中,创建一个文件并将以下代码复制到该文件中,以创建根模式:


// graphql/schemas/index.js

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

const rootType = gql`
 type Query {
     root: String
 }
 type Mutation {
     root: String
 }

`;

module.exports = [rootType];


Enter fullscreen mode Exit fullscreen mode
  • index.js在 resolvers 文件夹中,创建一个文件并将以下代码复制到该文件中,从而创建根解析器:


// graphql/resolvers/index.js

module.exports = [];


Enter fullscreen mode Exit fullscreen mode
  • 在上下文文件夹中,创建一个index.js文件并将以下代码复制到该文件中:


// graphql/context/index.js

module.exports = ({ req }) => {
  return {};
};


Enter fullscreen mode Exit fullscreen mode
  • 创建服务器。在api文件夹中创建一个server.js文件,并将以下代码复制到该文件中:


// api/server.js

const express = require('express');
const { createServer } = require('http');
const { ApolloServer } = require('apollo-server-express');
const cors = require('cors');
const typeDefs = require('../graphql/schemas');
const resolvers = require('../graphql/resolvers');
const context = require('../graphql/context');
const app = express();

app.use(cors());

const apolloServer = new ApolloServer({
  typeDefs,
  resolvers,
  context,
  introspection: true,
  playground: {
    settings: {
      'schema.polling.enable': false,
    },
  },
});

apolloServer.applyMiddleware({ app, path: '/api' });

const server = createServer(app);

module.exports = server;


Enter fullscreen mode Exit fullscreen mode

接下来,index.js在项目文件夹的根目录下创建一个文件,并将以下代码添加到该文件中:



// ./index.js

require('dotenv').config();

const server = require('./api/server');

const port = process.env.PORT || 3301;

process.on('uncaughtException', (err) => {
  console.error(`${(new Date()).toUTCString()} uncaughtException:`, err);
  process.exit(0);
});

process.on('unhandledRejection', (err) => {
  console.error(`${(new Date()).toUTCString()} unhandledRejection:`, err);
});


server.listen({ port }, () => console.log(
  `🚀 Server ready at http://localhost:${port}/api`,
));



Enter fullscreen mode Exit fullscreen mode

让我们添加启动脚本到package.json



...
"scripts": {
    "dev": "nodemon index.js",
    "start": "node index.js"
  },
...


Enter fullscreen mode Exit fullscreen mode

现在,我们准备启动服务器。



$ nm run dev

🚀 Server ready at http://localhost:3301/api


Enter fullscreen mode Exit fullscreen mode

你参观时http://localhost:3301/api会看到如下截图所示的游乐场:

GraphQL API 的 Playground

该测试平台是一个用于测试 GraphQL API 的图形用户界面。它还包含 API 的文档和模式。

使用 Sequelize 创建数据库迁移和模型

数据库架构图如下所示:

数据库模式图

如果您是 Sequelize 的新手,可以查看这篇关于 Sequelize 入门的文章。

  • 创建.sequelizerc文件“

$ touch .sequelizerc

- Copy the code below into the `.sequelizerc` file

Enter fullscreen mode Exit fullscreen mode

// ./sequelizerc

const path = require('path');

module.exports = {
"config": path.resolve('./database/config', 'config.js'),
"models-path": path.resolve('./database/models'),
"seeders-path": path.resolve('./database/seeders'),
"migrations-path": path.resolve('./database/migrations')
};

Next up, run the command below:

Enter fullscreen mode Exit fullscreen mode

$ npx sequelize-cli 初始化

The command above will create a `database` folder containing the migrations, models, seeds, and config folders.

We need to make a few changes to the `config/config.js` and `models/index.js` files as follows.

```js


// database/config/config.js

require('dotenv').config();

module.exports = {
  development: {
    username: 'root',
    password: null,
    database: 'database_development',
    host: '127.0.0.1',
    dialect: 'postgres',
    use_env_variable: 'DEV_DATABASE_URL',
  },
  test: {
    username: 'root',
    password: null,
    database: 'database_test',
    host: '127.0.0.1',
    dialect: 'postgres',
    host: '127.0.0.1',
    dialect: 'postgres',
    use_env_variable: 'TEST_DATABASE_URL',
  },
  production: {
    username: 'root',
    password: null,
    database: 'database_production',
    host: '127.0.0.1',
    dialect: 'postgres',
    host: '127.0.0.1',
    dialect: 'postgres',
    use_env_variable: 'DATABASE_URL',
  },
};



Enter fullscreen mode Exit fullscreen mode


// database/models/index.js

require('dotenv').config();

const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');

const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';

const config = require('../config/config')[env];

const db = {};

let sequelize;
if (config.use_env_variable) {
  sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
  sequelize = new Sequelize(
    config.database,
    config.username,
    config.password,
    config,
  );
}

fs.readdirSync(__dirname)
  .filter((file) => (
    file.indexOf('.') !== 0 && file !== basename && file.slice(-3) === '.js'
  ))
  .forEach((file) => {
    const model = sequelize.import(path.join(__dirname, file));
    db[model.name] = model;
  });

Object.keys(db).forEach((modelName) => {
  if (db[modelName].associate) {
    db[modelName].associate(db);
  }
});

db.sequelize = sequelize;
db.Sequelize = Sequelize;

module.exports = db;



Enter fullscreen mode Exit fullscreen mode

请记住将数据库名称替换为您的数据库名称。如果您使用的是 PostgreSQL 连接字符串,则可以将env连接字符串的名称传递给它use_env_variable

接下来运行以下命令,为数据库模式生成迁移:



$ npx sequelize-cli model:generate --name User --attributes name:string,email:string,password:string

$ npx sequelize-cli model:generate --name Post --attributes title:string,content:text,userId:integer

$ npx sequelize-cli model:generate --name Comment --attributes content:text,userId:integer,postId:integer


Enter fullscreen mode Exit fullscreen mode

userId让我们在postIdPosts 和 Comments 迁移中添加外键约束:



...
userId: {
        type: Sequelize.INTEGER,
        references: {
          model: {
            tableName: 'Users',
          },
          key: 'id',
        },
      },
...


Enter fullscreen mode Exit fullscreen mode


...
postId: {
        type: Sequelize.INTEGER,
        references: {
          model: {
            tableName: 'Posts',
          },
          key: 'id',
        },
      },

...



Enter fullscreen mode Exit fullscreen mode

接下来,我们来定义模型之间的关系,并按如下所示编辑模型:



// database/models/user.js


const bcrypt = require('bcryptjs');

module.exports = (sequelize, DataTypes) => {
  const User = sequelize.define(
    'User',
    {
      name: DataTypes.STRING,
      email: DataTypes.STRING,
      password: DataTypes.STRING,
    },
    {
      defaultScope: {
        rawAttributes: { exclude: ['password'] },
      },
    },
  );

  User.beforeCreate(async (user) => {
    user.password = await user.generatePasswordHash();
  });
  User.prototype.generatePasswordHash = function () {
    if (this.password) {
      return bcrypt.hash(this.password, 10);
    }
  };
  User.associate = function (models) {
    // associations can be defined here
    User.hasMany(models.Post, { foreignKey: 'userId', as: 'posts' });
  };
  return User;
};


Enter fullscreen mode Exit fullscreen mode

你明白上面这段代码的运行原理吗?

  • User定义了用户和帖子之间的多对多关系Post。一个用户可以发布多条帖子。
  • 添加了defaultScope选项,以确保在查询 User 模型时,密码不会作为 JSON 结果的一部分返回。
  • 新增了beforeCreate一个钩子,底层使用 bcrypt.js 自动对密码进行哈希处理。


// database/models/post.js

module.exports = (sequelize, DataTypes) => {
  const Post = sequelize.define(
    'Post',
    {
      title: DataTypes.STRING,
      content: DataTypes.TEXT,
      userId: DataTypes.INTEGER,
    },
    {},
  );
  Post.associate = function (models) {
    // associations can be defined here
    Post.belongsTo(models.User, { foreignKey: 'userId', as: 'author' });
    Post.hasMany(models.Comment, { foreignKey: 'postId', as: 'comments' });
  };
  return Post;
};


Enter fullscreen mode Exit fullscreen mode


// database/models/comment.js

module.exports = (sequelize, DataTypes) => {
  const Comment = sequelize.define(
    'Comment',
    {
      content: DataTypes.TEXT,
      userId: DataTypes.INTEGER,
      postId: DataTypes.INTEGER,
    },
    {},
  );
  Comment.associate = function (models) {
    Comment.belongsTo(models.User, { foreignKey: 'userId', as: 'author' });
    Comment.belongsTo(models.Post, { foreignKey: 'postId', as: 'post' });
  };
  return Comment;
};


Enter fullscreen mode Exit fullscreen mode

现在我们已经定义了模型之间的关系,我们将能够使用 Sequelize mixins,例如post.getAuthor()user.getPosts()等等。

如果您已经创建了数据库并提供了配置凭据,那么现在可以运行迁移了:



$ npx sequelize-cli db:migrate


Enter fullscreen mode Exit fullscreen mode

为用户、帖子和评论创建 GraphQL 模式

用户模式

在 schemas 文件夹中创建一个名为 `.schemas` 的文件user.js,并将以下代码复制到该文件中:



// graphql/schema/user.js

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

module.exports = gql`

 type User {
     id: Int!
     name: String!
     email: String!
     password: String!
     posts: [Post!]
 }

 extend type Mutation {
     register(input: RegisterInput!): RegisterResponse
     login(input: LoginInput!): LoginResponse
 }

 type RegisterResponse {
    id: Int!
    name: String!
    email: String!
 }

 input RegisterInput {
     name: String!
     email: String!
     password: String!
 }

input LoginInput {
     email: String!
     password: String!
 }

  type LoginResponse {
    id: Int!
    name: String!
    email: String!
    token: String!
 }
`;


Enter fullscreen mode Exit fullscreen mode

上面的代码片段讲的是什么?

  • 我们创建了一个type用户,并使用感叹号将所有字段设置为必填项。!
  • 在“用户”类型中,我们添加了一个posts返回“帖子”类型的字段,这样我们就可以查询用户创建的帖子。
  • [Post!]这意味着用户可以不发帖,但如果发帖,帖子类型必须是“帖子”。
  • 我们定义了两个变更操作:注册和登录,它们分别作为注册和登录的端点。

帖子架构

创建post.js文件schemas并将以下代码复制到该文件中:



// graphql/schemas/post.js

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

module.exports = gql`

 type Post {
     id: Int!
     title: String!
     content: String!
     author: User!
     comments: [Comment!]
     createdAt: String

 }

extend type Query {
    getAllPosts: [Post!]
    getSinglePost(postId: Int!): Post
}

 extend type Mutation {
     createPost(title: String!, content: String!): CreatePostResponse
 }

 type CreatePostResponse {
    id: Int!
    title: String!
    content: String!
    createdAt: String!
 }

`;


Enter fullscreen mode Exit fullscreen mode

我们再次创建了 Post 类型。这次我们添加了两个查询,分别用于获取所有帖子和单个帖子,以及一个用于创建新帖子的 mutation。

评论模式

创建comment.js文件schemas并将以下代码复制到该文件中:



// graphql/schemas/comment.js

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

module.exports = gql`

 type Comment {
     id: Int!
     content: String!
     author: User!
     post: Post!
     createdAt: String

 }

 extend type Mutation {
     createComment(content: String!, postId: Int!): CreateCommentResponse
 }

 type CreateCommentResponse {
    id: Int!
    content: String!
    createdAt: String!
 }

`;


Enter fullscreen mode Exit fullscreen mode

请注意,评论类型包含帖子和作者,分别返回帖子类型和用户类型。

最后,我们来更新根模式,schemas/index.js按如下所示进行更新:



// graphql/schemas/index.js

const { gql } = require('apollo-server-express');
const userType = require('./user')
const postType = require('./post')
const commentType = require('./comment')

const rootType = gql`
 type Query {
     root: String
 }
 type Mutation {
     root: String
 }

`;

module.exports = [rootType, userType, postType, commentType];


Enter fullscreen mode Exit fullscreen mode

接下来,我们将为模式创建解析器。

创建解析器

首先,我们来看一下解析器函数的结构。



 register(root, args, context, info) {

}


Enter fullscreen mode Exit fullscreen mode

上面的代码片段定义了一个名为 `resolver` 的解析器register。解析器函数接受四个参数:

  • :这是父解析器的结果。我们稍后会看到应用程序。
  • args:GraphQL 查询提供的参数或数据。这可以看作是 REST API 中的请求负载。
  • context:一个所有解析器均可访问的对象。任何需要对所有解析器全局访问的数据都会被放置在 context 中。例如,我们可以将 Sequelize 模型传递给 context。
  • info包含与正确查询相关的特定信息的对象。这仅在高级情况下有用。

现在我们已经了解了什么是解析器,让我们为 User 模式创建解析器。

用户解析器
让我们为 User 模式中的变更register和修改创建解析器。在 resolvers 文件夹中创建一个新文件,并将以下代码复制到其中:loginuser.js



// graphql/resolvers/user.js

const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');
const { AuthenticationError } = require('apollo-server-express');

const { User } = require('../../database/models');

module.exports = {
  Mutation: {
    async register(root, args, context) {
      const { name, email, password } = args.input;
      return User.create({ name, email, password });
    },

    async login(root, { input }, context) {
      const { email, password } = input;
      const user = await User.findOne({ where: { email } });
      if (user && bcrypt.compareSync(password, user.password)) {
        const token = jwt.sign({ id: user.id }, 'mySecret');
        return { ...user.toJSON(), token };
      }
      throw new AuthenticationError('Invalid credentials');
    },
  },
};



Enter fullscreen mode Exit fullscreen mode

在注册解析器中,我们从 args 对象中提取有效负载,并使用 User 模型创建新用户。在验证过程中login,我们对用户进行身份验证,如果提供了正确的凭据,则返回用户以及一个 JSON Web Token;否则,抛出身份验证错误。

注意:以上代码片段仅用于演示目的,不建议在生产环境中使用。在实际应用中,您需要验证输入、将 JWT 密钥存储在指定位置.env,并遵循其他安全最佳实践。

现在,当您在测试环境中测试这些变更(端点)时,您将获得与以下屏幕截图类似的结果:

在 GraphQL Playground 上注册变异测试结果

GraphQL Playground 上的登录变更测试结果

上述屏幕截图对应的 graphQL 查询可以在文章存储库中包含的 query.graphql 文件中找到

请求身份验证

在创建 POST 解析器之前,我们需要设计一种请求身份验证机制。我们希望添加一个函数来检查请求头中是否包含授权令牌。

按如下方式编辑graphql/context/index.js



// graphql/context/index.js

const { User } = require('../../database/models');
const jwt = require('jsonwebtoken');
const { AuthenticationError } = require('apollo-server-express')

const verifyToken = async (token) => {
  try {
    if (!token) return null;
    const { id } = await jwt.verify(token, 'mySecret');
    const user = await User.findByPk(id);
    return user;
  } catch (error) {
    throw new AuthenticationError(error.message);
  }
};

module.exports = async ({ req }) => {
  const token = (req.headers && req.headers.authorization) || '';
   const user = await verifyToken(token)
  return { user };
};



Enter fullscreen mode Exit fullscreen mode

从上面的代码片段可以看出,我们创建了一个辅助函数verifyToken,用于验证令牌并返回令牌中包含的用户 ID。在上下文函数中,我们检查请求头中的授权信息,如果存在令牌,则对其进行解码,并将解码后的user对象传递给上下文。现在,我们可以检查user上下文中的对象,以确定请求是否已通过身份验证。

首先
,我们来创建createPost解析器。新建一个文件,post.js并将graphql/resolvers以下代码复制到其中:



// graphql/resolvers/post.js

const { Post } = require('../../database/models');

const { AuthenticationError } = require('apollo-server-express');

module.exports = {
  Mutation: {
    async createPost(_, { content, title }, { user = null }) {
      if (!user) {
        throw new AuthenticationError('You must login to create a post');
      }
      return Post.create({
        userId: user.id,
        content,
        title,
      });
    },
  },
};



Enter fullscreen mode Exit fullscreen mode

我们解构了 args 对象以获取请求的内容和标题。同样地,我们解构了 context 对象以获取请求用户,如果 user 为 null,则表示请求未通过身份验证。下面的屏幕截图显示了在 Playground 上测试的结果。 仔细查看上面的屏幕截图,您可以看到授权是如何添加的。要在 GraphQL Playground 中添加授权标头,请转到 Playground 底部,单击并添加授权,如下所示:
playground 上的 createPost 解析器
HTTP HEADERS



{
  "Authorization": "your-json-web-token"
}


Enter fullscreen mode Exit fullscreen mode

您可以通过我们之前创建的 mutation 获取令牌login

接下来,我们来创建 `and`getAllPosts和 ` getSinglePost.` 的解析器。按如下方式编辑 ` post.js.`:



// graphql/resolvers/post.js

const { Post } = require('../../database/models');

const { AuthenticationError } = require('apollo-server-express');

module.exports = {
  Mutation: {
    async createPost(_, { content, title }, { user = null }) {
      if (!user) {
        throw new AuthenticationError('You must login to create a post');
      }
      return Post.create({
        userId: user.id,
        content,
        title,
      });
    },
  },

  Query: {
    async getAllPosts(root, args, context) {
      return Post.findAll();
    },
    async getSinglePost(_, { postId }, context) {
      return Post.findByPk(postId);
    },
  },

  Post: {
    author(post) {
      return post.getAuthor();
    },

    comments(post) {
      return post.getComments();
    },
  },
};



Enter fullscreen mode Exit fullscreen mode

请注意,我们为 Post 本身添加了一个解析器,在 Post schema 中,我们有 `post`author和 `post_comments` comments。这里我们使用了根对象,post在本例中是 `post`。GraphQL 会隐式地将 Post 解析为 Post 查询的结果,并将 post 对象作为根对象传递。然后,我们使用 Sequelize mixin来返回 Post 的相关作者和评论。

我们来测试一下这个getAllPosts查询。你可以在 Playground 中添加下面的示例查询。



query allPosts {
  getAllPosts {
    id
    title
    content
    author {
      id
      name
    }
    comments {
      id
      content
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

上述查询的响应如下所示:



{
  "data": {
    "getAllPosts": [
      {
        "id": 1,
        "title": "New post",
        "content": "New post content",
        "author": {
          "id": 1,
          "name": "test"
        },
        "comments": []
      }
    ]
  }
}


Enter fullscreen mode Exit fullscreen mode

查看响应可知,查询返回了数据库中唯一的帖子以及帖子的作者,评论数组为空,因为该帖子目前还没有评论。

同样地,下面的查询展示了如何查询单个帖子。



query singlePost {
  getSinglePost(postId: 1) {
    id
    title
    content
    author {
      name
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

您可以根据自己的需求来定义查询语句。

评论解析器
在评论模式中,我们有一个createCommentmutation,让我们为其创建一个解析器。
将以下代码片段复制到graphql/resolvers/comment.js



// graphql/resolvers/comment.js

const { Post } = require('../../database/models');

const { AuthenticationError, ApolloError } = require('apollo-server-express');

module.exports = {
  Mutation: {
    async createComment(_, { content, postId }, { user = null }) {
      if (!user) {
        throw new AuthenticationError('You must login to create a comment');
      }

      const post = await Post.findByPk(postId);

      if (post) {
        return post.createComment({ content, userId: user.id });
      }
      throw new ApolloError('Unable to create a comment');
    },
  },

  Comment: {
    author(comment) {
      return comment.getAuthor();
    },
    post(comment) {
      return comment.getPost();
    },
  },
};


Enter fullscreen mode Exit fullscreen mode

与帖子解析器类似,我们确保只有经过身份验证的用户才能创建评论,并且我们还通过首先检索帖子,然后使用 Sequelize 提供的关系方法为给定的帖子创建评论来确保帖子存在。

下面展示了一个创建评论的示例查询,请将其复制并粘贴到 Playground 中以测试变更。请记住在授权标头中添加身份验证令牌。



mutation createComment {
   createComment(content: "New post comment", postId: 1) {
    id
    content
    createdAt
  }
}


Enter fullscreen mode Exit fullscreen mode

回复如下所示:



{
  "data": {
    "createComment": {
      "id": 5,
      "content": "New post comment",
      "createdAt": "1593345238316"
    }
  }
}


Enter fullscreen mode Exit fullscreen mode

现在,当您查询帖子时,您将能够获得该帖子的评论结果。

请记住将解析器导入到根解析器中graphql/resolvers/index.js。现在它应该看起来像下面这样:



// graphql/resolvers/index.js

const userResolvers = require('./user');
const postResolvers = require('./post');
const commentResolvers = require('./comment');

module.exports = [userResolvers, postResolvers, commentResolvers];


Enter fullscreen mode Exit fullscreen mode

就是这样。完整的代码可以在这里找到。

结论

如果你按照步骤操作到这里,你就已经成功地使用 GraphQL 开发了一个 API。值得注意的是,本文采用极简主义方法,侧重于最重要的内容,而非最佳实践。我们使用了 PostgreSQL 和 Sequelize ORM 作为数据源,但你可以根据应用程序的实际情况选择任何合适的数据源。

接下来呢?

接下来的文章中,我将介绍如何使用中间件改进应用程序,如何在 GraphQL API 上实现集成测试等等。敬请期待!

更多资源

文章来源:https://dev.to/nedsoft/build-api-with-graphql-node-js-and-sequelize-5e8e