使用Hapi.js,Mongoose和MongoDB构建RESTAPI

1,041 阅读5分钟

原文地址:https://www.thepolyglotdeveloper.com/2019/02/using-hapijs-mongoose-mongodb-build-rest-api/

在本教程中,我们将使用Hapi.js,Joi和Mongoose作为后端框架以及MongoDB作为NoSQL数据库来开发一个简单的RESTful API 。通过简化功能并验证客户端提供的数据。

创建Hapi.js项目

创建一个新的项目目录并执行以下命令,并安装支持数据验证的joi和连接mongoDB数据库的mongoose依赖模块:

npm init -y
npm install hapi joi mongoose --save

上面的命令将创建一个新的package.json文件,并安装Hapi.js框架,Joi验证框架和Mongoose对象文档建模器(ODM)。

我们将所有应用程序代码添加到单个项目文件中。创建一个app.js文件,包含以下JavaScript代码:

const Hapi = require("hapi");
const Mongoose = require("mongoose");
const Joi = require("joi");

const server = new Hapi.Server({ "host": "localhost", "port": 3000 });

server.route({
    method: "POST",
    path: "/person",
    options: {
        validate: {}
    },
    handler: async (request, h) => {}
});

server.route({
    method: "GET",
    path: "/people",
    handler: async (request, h) => {}
});

server.route({
    method: "GET",
    path: "/person/{id}",
    handler: async (request, h) => {}
});

server.route({
    method: "PUT",
    path: "/person/{id}",
    options: {
        validate: {}
    },
    handler: async (request, h) => {}
});

server.route({
    method: "DELETE",
    path: "/person/{id}",
    handler: async (request, h) => {}
});

server.start();

我们已经在app.js文件中添加了很多代码。实质上,我们已经导入了下载的依赖项,定义了服务器设置,定义了也称为接口的路由,并启动了服务器。

您会注意到,并非我们的所有路由都是一样的。我们正在开发基于创建,检索,更新和删除(CRUD)的REST API,并在一些接口上进行了验证。特别是,我们将向接口添加验证逻辑,以将数据保存到数据库,而不是检索或删除。

接下来,让我们看一下如何配置MongoDB并添加我们的接口逻辑。

使用Mongoose ODM与数据库进行交互

记住,我假设您已经可以访问MongoDB实例。在定义服务器配置之后,在app.js文件的顶部,我们需要连接到MongoDB。插入以下行以建立连接:

Mongoose.connect("mongodb://localhost/thepolyglotdeveloper");

您需要将连接字符串信息换成您自己的连接字符串信息。在使用Mongoose时,我们需要为每个集合定义一个模型。由于这是一个简单的示例,因此我们只有一个模型,它看起来如下所示:

const PersonModel = Mongoose.model("person", {
    firstname: String,
    lastname: String
});

我们的每个文档都包含一个firstname和一个lastname,但是两个字段都不是必需的。这些文档将保存到people ODM模型的复数形式的集合中。

此时,可以使用MongoDB了。

现在是时候开始开发我们的API接口了,因此从创建接口开始,我们可能会有类似这样的内容:

server.route({
    method: "POST",
    path: "/person",
    options: {
        validate: {}
    },
    handler: async (request, h) => {
        try {
            var person = new PersonModel(request.payload);
            var result = await person.save();
            return h.response(result);
        } catch (error) {
            return h.response(error).code(500);
        }
    }
});

我们现在已经跳过了验证逻辑,但是在我们内部,handler我们将接收与客户端请求一起发送的有效负载数据,并创建模型的新实例。使用我们的模型,我们可以保存到数据库并将响应返回给客户端。mongoose将基于schema对有效负载数据进行基本验证,但我们可以做得更好。这是在Hapi.js中安装Joi模块的原因。

让我们看一下validate路线中的对象:

validate: {
    payload: {
        firstname: Joi.string().required(),
        lastname: Joi.string().required()
    },
    failAction: (request, h, error) => {
        return error.isJoi ? h.response(error.details[0]).takeover() : h.response(error).takeover();
    }
}

validate对象中,我们选择验证payload。我们还可以选择验证请求的params以及query,但此处都不需要。尽管我们可以执行一些非常复杂的验证,但我们只是在验证两个属性是否都存在。我们不会使用缺失的错误返回给用户一个模糊的错误,而是使用failActionwhich是可选的来返回确切的错误。

现在,让我们看一下如何检索已创建的数据。在典型的CRUD方案中,我们可以检索所有数据或特定数据。我们将适应两种情况。

server.route({
    method: "GET",
    path: "/people",
    handler: async (request, h) => {
        try {
            var person = await PersonModel.find().exec();
            return h.response(person);
        } catch (error) {
            return h.response(error).code(500);
        }
    }
});

上面的路由将find在Mongoose中执行该函数,而无需任何查询参数。这意味着没有条件可搜索从集合返回所有数据的结果。同样,我们可以返回特定的数据。

如果要返回特定的数据,可以在find函数中提供参数,也可以使用以下命令:

server.route({
    method: "GET",
    path: "/person/{id}",
    handler: async (request, h) => {
        try {
            var person = await PersonModel.findById(request.params.id).exec();
            return h.response(person);
        } catch (error) {
            return h.response(error).code(500);
        }
    }
});

在上述接口中,我们接受id路由参数并正在使用该findById函数。从交互返回的数据将返回给面向客户端的应用程序。

随着创建和检索接口的完成,我们可以以更新和删除接口结束本教程。从更新终结点开始,我们可能会有类似以下内容:

server.route({
    method: "PUT",
    path: "/person/{id}",
    options: {
        validate: {
            payload: {
                firstname: Joi.string().optional(),
                lastname: Joi.string().optional()
            },
            failAction: (request, h, error) => {
                return error.isJoi ? h.response(error.details[0]).takeover() : h.response(error).takeover();
            }
        }
    },
    handler: async (request, h) => {
        try {
            var result = await PersonModel.findByIdAndUpdate(request.params.id, request.payload, { new: true });
            return h.response(result);
        } catch (error) {
            return h.response(error).code(500);
        }
    }
});

就像创建接口一样,我们正在验证数据。但是,我们的验证与先前的接口有点不同。并不是说我们的属性是必需的,我们只是说它们是可选的。当我们执行此操作时,显示的任何不在验证器中的属性都将引发错误。因此,例如,如果我想包含一个中间名,它将失败。

handler函数内部,我们可以使用称为的快捷函数findByIdAndUpdate,该函数使我们能够找到文档以相同的操作进行更新和更新,而不是分两步进行。我们包括new设置,以便可以将最新的文档信息返回给客户端。

删除接口将简单得多:

server.route({
    method: "DELETE",
    path: "/person/{id}",
    handler: async (request, h) => {
        try {
            var result = await PersonModel.findByIdAndDelete(request.params.id);
            return h.response(result);
        } catch (error) {
            return h.response(error).code(500);
        }
    }
});

使用id从客户端传递的参数,我们可以执行findByIdAndDelete函数,该函数将通过id查找文档,然后一举删除它,而不用分两步进行。

到目前为止,您应该已经可以使用该API。在尝试与Angular或Vue.js之类的前端框架一起使用之前,您可能想要使用Postman之类的工具进行接口调试。