搜了国内的文章 使用 Hapi
开发 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 是一个 Node.js 的 web 框架,即一个构建应用程序和服务丰富的框架。 目前 Hapi v17 仅支持 node v8.9.0 及以上版本
- 认证和授权:内置的验证和授权方案
- 缓存:提供客户端和服务端缓存,catbox
- 路由: 内置路由
- 验证:使用 Joi
- Cookies:提供了配置选项处理 cookie
- 日志:置的日志记录
- 错误处理:boom
- 进程监控:hapi 插件 good
- 安装 Node.js v8.9.0 及以上
- 安装 MongoDB
- 熟悉数据库概念和 JavaScript 的应用知识
-
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 完整的更新日志
在这个示例中提供 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
demo
├── package.json
├── server.js
├── .gitignore (可选)
├── src
| ├
│ ├── controller
│ | └── test.js
│ └─── models
│ └── test.js
│
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');
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 };
});
});
}
'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)
}
})();
使用 postman 测试 http://localhost:3000/demos
- 登录 https://manage.auth0.com
- 进入 https://manage.auth0.com/#/apis, 创建api name: Demo API identifier: https://mydemoapi.com Signing Algorithm: RS256
- 修改 server.js
'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);
});
- 替换
jwksUri
和YOUR-API-AUDIENCE-ATTRIBUTE
和YOUR-AUTH0-DOMAIN
- 进入 https://manage.auth0.com/#/apis/5b5d918044b2ba73d9d733a0/test
- postman 测试时候 header要加 authorization 字段,值为 (参考Sending the token to the API中例子的)