使用 hapi 创建 restfulAPI

Posted 之梦科技公司

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用 hapi 创建 restfulAPI相关的知识,希望对你有一定的参考价值。

搜了国内的文章 使用 `Hapi` 开发 `RESTful API` 的很少很少,一些 社区介绍的部分在新版本中已经不在适用,新版本中有很多变化


## 什么是 `RESTful API`

>RESTful API: REST -- REpresentational State Transfer,英语的直译就是“表现层状态转移”


可以参考下面理解: 

- 理解本身的 REST 架构风格:http://www.infoq.com/cn/articles/understanding-restful-style/


- 理解 RESTful 架构:http://www.ruanyifeng.com/blog/2011/09/restful.html


- Restful API 设计指南:http://www.ruanyifeng.com/blog/2014/05/restful_api.html


## Hapi

  Hapi 是一个 Node.js 的 web 框架,即一个构建应用程序和服务丰富的框架。

  目前 Hapi v17 仅支持 node v8.9.0 及以上版本

- 了解:[Node.js中 hapi-express-restify-koa 性能对比](https://raygun.com/blog/nodejs-vs-hapi-express-restify-koa/)

## Hapi 特性

- 认证和授权:内置的验证和授权方案

- 缓存:提供客户端和服务端缓存,[catbox](https://github.com/hapijs/catbox)

- 路由: 内置路由

- 验证:使用 Joi

- Cookies:提供了配置选项处理 cookie

- 日志:置的日志记录

- 错误处理:[boom](https://github.com/hapijs/boom)

- 进程监控:hapi 插件 [good](https://github.com/hapijs/good)


## 使用 Hapi 的必要条件

- 安装 Node.js v8.9.0 及以上

- 安装 MongoDB

- 熟悉数据库概念和 javascript 的应用知识


## Hapi v17 新的变化

- server.connection 方法移除,现在使用

  ```

  const server = new Hapi.Server({

      host: 'localhost',

      port: 3000

  })

  ```

- 开启关闭服务方法 完全异步,没有回调

  ```

  try {

      await server.start()

  }

  catch (err) {

      console.log(err)

  }

    

  try {

      await server.stop()

  }

  catch (err) {

      console.log(err)

  }

  ```


- reply() 回调方法移除,response.hold() 和 response.resume() 方法不在可用

  ```

  // 以前

  const handler = function (request, reply) {

    return reply('ok');

  };


  // 现在


  const handler = function (request, h) {

    return 'ok';

  };

  ```

  更多使用 h 的示例如下:  

  ```

  const handler = (request, h) => {

    // return a string

    return 'ok'


    // return an object and hapi creates JSON out of it

    return { name: 'Authentication Library', library: true }


    // redirect to 404

    return h.redirect('/404')


    // return a view

    return h.view('index', { name: 'Authentication Library' })


    // use the "h" response toolkit to create a response

    return h

     .response(thisHTML)

      .type('text/html')

      .header('X-Custom', 'my-value')

      .code(201)

  }

  ```

- 三个请求事件类型 request, request-interval,  request-error 合并成一个单一的 request 事件

- 触发的方法 像 server.on, request.on, response.on 被替换,使用server.events.on(), request.events.on(), response.events.on() 替代

- 新的请求扩展:

  ```

  server.ext('onPreAuth', (request, h) => { … })

  server.ext('onCredentials', (request, h) => { … })

  server.ext('onPostAuth', (request, h) => { … }) ]

  ```

- 在路由定义的时候 替换 config 为 options

  ```

  server.route({

    method: 'POST',

    path: '/',

    options: { … }

  })

  ```

- 插件

  目前使用 

  ```

  exports.plugin = { register, name, version, multiple, dependencies, once, pkg }

  ```

更多变化详见[GitHub 完整的更新日志](https://github.com/hapijs/hapi/issues/3658)


## 正题:使用 Hapi 构建一个简单的 api

在这个示例中提供 CURD 操作

- Get all demos - GET /demos

- Get one demo - GET /demo/11

- Create a demo - POST /demos

- Edit a demo - PUT /demos/11

- Delete a demo - DELETE /demos/11


A demo 有下面的属性:


- name

- age


### 1 创建项目目录 demo,最后结构如下:

```

demo

├── package.json

├── server.js 

├── .gitignore (可选)

└── src

    ├

    ├── controller

    |   └── test.js

    └─── models

        └── test.js

   

```

### 2 编写 model

```

src/models/test.js

'use strict';


const mongoose = require('mongoose');

const Schema = mongoose.Schema;


const demoModel = new Schema({

  name: { type: String, required: true, index: { unique: true } },

  age: { type: Number, required: true }

});


module.exports = mongoose.model('Demo', demoModel, 'demos'); 

```

### 编写 controllers 

```

src/controllers/test.js


var Demo =  require('../models/test');


/**

 * List Demos

 */

exports.list = (req, h) => {

  return Demo.find({}).exec().then((demo) => {


    return { demos: demo };


  }).catch((err) => {


    return { err: err };


  });

}


/**

 * Get Demo by ID

 */

exports.get = (req, h) => {


  return Demo.findById(req.params.id).exec().then((demo) => {


    if(!demo) return { message: 'Demo not Found' };


    return { demos: demo };


  }).catch((err) => {


    return { err: err };


  });

}



/**

 * POST a Demo

 */

exports.create = (req, h) => {


  const demoData = {

    name: req.payload.name,

    age: req.payload.age

  };


  return Demo.create(demoData).then((demo) => {


     return { message: "Demo created successfully", demo: demo };


  }).catch((err) => {


    return { err: err };


  });

}


/**

 * PUT | Update Demo by ID

 */

exports.update = (req, h) => {


  return Demo.findById(req.params.id).exec().then((demo) => {


    if (!demo) return { err: 'Demo not found' };


    demo.name = req.payload.name;

    demo.breed = req.payload.breed;

    demo.age = req.payload.age;

    demo.image = req.payload.image;


    demo.save(dogData);


  }).then((data) => {


      return { message: "Demo data updated successfully" };


  }).catch((err) => {


      return { err: err };


  });

}


/**

 * Delete Demo by ID

 */

exports.remove = (req, h) => {


  return Demo.findById(req.params.id).exec(function (err, demo) {


    if (err) return { dberror: err };

    if (!demo) return { message: 'Demo not found' };


    demo.remove(function (err) {

      if (err) return { dberror: err };


      return { success: true };

    });

  });

}

```


### 编写 server.js

```

'use strict';


const Hapi = require('hapi');

const mongoose = require('mongoose');

const DemoController =  require('./src/controllers/test');

const MongoDBUrl = 'mongodb://localhost:27017/demoapi';


const server = new Hapi.Server({

  port: 3000,

  host: 'localhost'

});


server.route({

  method: 'GET',

  path: '/demos',

  handler: DemoController.list

});


server.route({

  method: 'GET',

  path: '/demos/{id}',

  handler: DemoController.get

});

server.route({

  method: 'POST',

  path: '/demos',

  handler: DemoController.create

});


server.route({

  method: 'PUT',

  path: '/demos/{id}',

  handler: DemoController.update

});


server.route({

  method: 'DELETE',

  path: '/demos/{id}',

  handler: DemoController.remove

});


(async () => {

  try {  

    await server.start();

    // Once started, connect to Mongo through Mongoose

    mongoose.connect(MongoDBUrl, {}).then(() => { console.log(`Connected to Mongo server`) }, err => { console.log(err) });

    console.log(`Server running at: ${server.info.uri}`);

  }

  catch (err) {  

    console.log(err)

  }

})();

```


### 测试 API


使用 postman 测试 `http://localhost:3000/demos`


- [demo](https://github.com/fairyly/Hapi-demo)


### 增加授权验证

目前步骤有点多,还需要注册一个平台,暂不做详细说明了



## 参考

- GitHub:https://github.com/hapijs/hapi

- website: https://hapijs.com/

- issue:https://github.com/hapijs/discuss/issues

- [developing-restful-apis-with-hapi v17](https://auth0.com/blog/developing-restful-apis-with-hapijs/)

- https://github.com/hapijs/hapi/issues/3658



## 仅供学习交流


以上是关于使用 hapi 创建 restfulAPI的主要内容,如果未能解决你的问题,请参考以下文章

ionic 后台Api服务, 使用rest-hapi , node.js 创建 RESTful API Service , 附完整源代码

将 Javascript Fetch API 与 Hapi 一起使用

分离前端和后端 - 使用 Vue/Webpack/Hapi 进行持久身份验证

使用 Nodejs (Hapi.js) 和 Prisma 的 Docker 中的 ConnectionError

Nodejs + Hapi + SSL/TLS + OAUTH2 + JWT 作为承载

Hapi 嵌套路由