使用 Hapi 开发 RESTful APIs

6,594 阅读3分钟

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

什么是 RESTful API

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

可以参考下面理解:

Hapi

Hapi 是一个 Node.js 的 web 框架,即一个构建应用程序和服务丰富的框架。 目前 Hapi v17 仅支持 node v8.9.0 及以上版本

Hapi 特性

  • 认证和授权:内置的验证和授权方案
  • 缓存:提供客户端和服务端缓存,catbox
  • 路由: 内置路由
  • 验证:使用 Joi
  • Cookies:提供了配置选项处理 cookie
  • 日志: 内置的日志记录
  • 错误处理:boom
  • 进程监控:hapi 插件 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 完整的更新日志

正题:使用 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

增加授权验证

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

参考

仅供学习交流