Skip to content

Latest commit

 

History

History
443 lines (355 loc) · 9.79 KB

1.2.1 developing-restful-apis-with-hapi v17》.md

File metadata and controls

443 lines (355 loc) · 9.79 KB

使用 Hapi 开发 RESTful APIs

搜了国内的文章 使用 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 dogs - GET /dogs

  • Get one dog - GET /dog/11

  • Create a dog - POST /dogs

  • Edit a dog - PUT /dogs/11

  • Delete a dog - DELETE /dogs/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

添加授权

'use strict';

const Hapi = require('hapi');
const mongoose = require('mongoose');
const hapiAuthJWT = require('hapi-auth-jwt2');
const jwksRsa = require('jwks-rsa');
const DemoController =  require('./src/controllers/demo');
const MongoDBUrl = 'mongodb://localhost:27017/demoapi';

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


const validateUser = (decoded, request, callback) => {
  // This is a simple check that the `sub` claim
  // exists in the access token. Modify it to suit
  // the needs of your application
  console.log("Decoded", decoded);
  if (decoded && decoded.sub) {
    return callback(null, true, {});
  }

  return callback(null, false, {});
}

const registerRoutes = () => {
  server.route({
    method: 'GET',
    path: '/demos',
    options: {
      handler: DemoController.list
    }
  });

  server.route({
    method: 'GET',
    path: '/demos/{id}',
    options: {
      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
  });
}

const init = async() => {
  await server.register(hapiAuthJWT);
  // see: http://hapijs.com/api#serverauthschemename-scheme
  server.auth.strategy('jwt', 'jwt', { 
    key: jwksRsa.hapiJwt2Key({
      cache: true,
      rateLimit: true,
      jwksRequestsPerMinute: 5,
      jwksUri: 'https://fairyly.auth0.com/.well-known/jwks.json'
    }),
    verifyOptions: { 
      audience: 'https://mydogapi.com',
      issuer: "https://fairyly.auth0.com/",
      algorithms: ['RS256']
    },
    validate: validateUser
  });

  server.auth.default('jwt');

  registerRoutes();

  await server.start();
  return server;

};

init().then(server => {
  console.log('Server running at:', server.info.uri);
}).catch(err => {
  console.log(err);
});

参考

仅供学习交流