はじめに
Node.js + Express + あえてCloudant で REST APIを作成する(開発編①)の続きです。
CRUDを行うサンプルコードを開発してローカル環境とBluemix環境で実行します。
CRUDのサンプルコード
- メイン・スクリプトを編集:
- ここでcloudantClient.jsを読み込み、/animalsとパスを切って, cloudantClient.jsを実行させます。
- パスの指定方法がREST API設計では重要です。
app.js
var express = require('express');
var path = require('path');
var favicon = require('serve-favicon');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var routes = require('./routes/index');
// 今回追加部分
var cloudantClient = require('./routes/cloudantClient');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'e');
// uncomment after placing your favicon in /public
//app.use(favicon(path.join(__dirname, 'public', 'favicon.ico')));
app.use(logger('dev'));
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use('/', routes);
// 今回追加部分
app.use('/animals',cloudantClient);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
var err = new Error('Not Found');
err.status = 404;
next(err);
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
- 今回開発したコード全体:
cloudantClient.js
require('dotenv').load();
var express = require('express');
var router = express.Router();
// Cloudantの認証とサンプルのanimaldbの指定
var Cloudant = require('cloudant');
var me = process.env.cloudant_username; // Set this in .env file
var password = process.env.cloudant_password; // Set this in .env file
var cloudant = Cloudant({account:me, password:password});
var db = cloudant.db.use("animaldb");
// GETでドキュメント参照とエラーハンドリング
router.get('/:_id', function(req, res, next){
db.get(req.params._id, function(err, data){
if(!err){
res.set('X-RateLimit-Limit','180');
res.status(200).json({"document":data});
//res.status(200).send(JSON.stringify(data));
console.log(data);
}
else if(err.statusCode == 404){
console.log("Error:", err.statusCode);
res.set('X-RateLimit-Limit','180');
res.status(404).json({"error":{"code":404,"message":"Resource not found, Specify the correct id","info":"https://docs.cloudant.com/http.html#http-status-codes"}});
}
else if(err.statusCode == 500){
console.log("Error:", err.statusCode);
res.set('X-RateLimit-Limit','180');
res.status(500).json({"error":{"code":500,"message":"Internal Server Error,Please retry later","info":"https://docs.cloudant.com/http.html#http-status-codes"}});
}
else{
console.log("Error:", err);
res.set('X-RateLimit-Limit','180');
res.status(err.statusCode).json({"error":{"code":err.statusCode,"message":"Other error","info":"https://docs.cloudant.com/http.html#http-status-codes"}});
}
});
});
//POSTでドキュメント作成とエラーハンドリング
router.post('/', function(req, res, next) {
db.insert(req.body, req.body._id, function(err, data){
if(!err){
var id = req.body._id;
var url = './animals/'+ id;
res.set('X-RateLimit-Limit','180');
res.status(201).json({"created":data,"url":url});
//res.status(201).send(JSON.stringify(data));
console.log(data);
}
else if(err.statusCode == 409){
console.log("Error:", err.statusCode);
res.set('X-RateLimit-Limit','180');
res.status(409).json({"error":{"code":409, "message":"Resource already exists","info":"https://docs.cloudant.com/http.html#http-status-codes"}});
}
else if(err.statusCode == 500){
console.log("Error:", err.statusCode);
res.set('X-RateLimit-Limit','180');
res.status(500).json({"error":{"code":500,"message":"Internal Server Error,Please retry later","info":"https://docs.cloudant.com/http.html#http-status-codes"}});
}
else{
console.log("Error:", err);
res.set('X-RateLimit-Limit','180');
res.status(err.statusCode).json({"error":{"code":err.statusCode,"message":"Other error","info":"https://docs.cloudant.com/http.html#http-status-codes"}});
}
});
});
//PUTでドキュメント更新とエラーハンドリング
router.put('/:_id', function(req, res, next){
db.insert(req.body, function(err, data){
if(!err){
res.set('X-RateLimit-Limit','180');
res.status(200).json({"updated":data});
//res.status(200).send(JSON.stringify(data));
console.log(data);
}
else if(err.statusCode == 409){
console.log("Error:", err.statusCode);
res.set('X-RateLimit-Limit','180');
res.status(409).json({"error":{"code":409,"message":"Update failed, specify the correct id and rev","info":"https://docs.cloudant.com/http.html#http-status-codes"}});
}
else if(err.statusCode == 500){
console.log("Error:", err.statusCode);
res.set('X-RateLimit-Limit','180');
res.status(500).json({"error":{"code":500,"message":"Internal Server Error,Please retry later","info":"https://docs.cloudant.com/http.html#http-status-codes"}});
}
else{
console.log("Error:", err);
res.set('X-RateLimit-Limit','180');
res.status(err.statusCode).json({"error":{"code":err.statusCode,"message":"Other error","info":"https://docs.cloudant.com/http.html#http-status-codes"}});
}
});
});
// DELETEでドキュメント削除とエラーハンドリング
router.delete('/:_id/:_rev', function(req, res, next){
db.destroy(req.params._id,req.params._rev, function(err, data){
if(!err){
res.set('X-RateLimit-Limit','180');
res.json({"deleted":data});
res.status(204);
console.log(data);
}
else if(err.statusCode == 404){
console.log("Error:", err.statusCode);
res.set('X-RateLimit-Limit','180');
res.status(404).json({"error":{"code":404,"message":"Resource not found, Specify the correct id and rev","info":"https://docs.cloudant.com/http.html#http-status-codes"}});
}
else if(err.statusCode == 409){
console.log("Error:", err.statusCode);
res.set('X-RateLimit-Limit','180');
res.status(409).json({"error":{"code":409,"message":"Delete failed, specify the correct id and rev","info":"https://docs.cloudant.com/http.html#http-status-codes"}});
}
else if(err.statusCode == 500){
console.log("Error:", err.statusCode);
res.set('X-RateLimit-Limit','180');
res.status(500).json({"error":{"code":500,"message":"Internal Server Error,Please retry later","info":"https://docs.cloudant.com/http.html#http-status-codes"}});
}
else{
console.log("Error:", err);
res.set('X-RateLimit-Limit','180');
res.status(err.statusCode).json({"error":{"code":err.statusCode,"message":"Other error","info":"https://docs.cloudant.com/http.html#http-status-codes"}});
}
});
});
module.exports = router;
動かしてみる
以下のコマンドでapp.jsを実行します。
$ npm start
Chrome Postmanからアクセス:作成
Chrome Postmanからアクセス:参照
Chrome Postmanからアクセス:更新
- URLは/animals/catを指定し、ボディに以下のJSONを入れ、application/Jsonを指定し、PUTリクエスト送信&結果:
- POSTで作成したときの違いはボディに_revが入っていることです。あとは各項目好きなところを更新します。
- レスポンス・ボディで_rev番号が更新されていることがわかります。
Chrome Postmanからアクセス:削除
Bluemixにデプロイしてみる
$ cd AppName // アプリのディレクトリに移動
$ cf push AppName // BluemixにCLIでログインしてあるのが前提です
- http://<hostname.mybluemix.net>/animals/ に対して、同様にPostmanから各HTTPメソッドを試して問題ないことを確認します。
- Catドキュメントに対するGETリクエスト送信&結果:
まとめ
以上で(あえて)Cloudant NoSQL DBのサンプルDBに対してCRUD操作のREST APIをNode.jsで実装してみました。
コードは初級レベルなので、よりよい書き方がありましたらコメントいただけますと幸いです。
つまったところ・注意点など
- node app.js の起動方法はExpress3、npm start の起動方法はExpress4、といったように、お作法がバージョンで結構違いますが、ネット記事などの情報はExpress3ベースがまだ多いので、検索するときは注意が必要です。
- Firefox PosterだとJSONをPOSTするときに意図しない改行コードなど入ることがあり、意図したとおりのデータ挿入ができなかったため、Chrome Postmanで実行したところ成功しました。これからはPostmanを使うつもりです。
参考文献
- https://www.npmjs.com/package/cloudant
- https://www.npmjs.com/package/express
- https://github.com/cloudant/nodejs-cloudant/blob/master/example/crud.js
- https://github.com/cloudant-labs/cloudant-nano#document-functions
- http://expressjs.com/
- https://developer.mozilla.org/ja/docs/Web/JavaScript/Guide/Functions
- https://howtonode.org/bogart-couchdb
- https://console.ng.bluemix.net/docs/services/Cloudant/api/document.html
- https://blog.risingstack.com/10-best-practices-for-writing-node-js-rest-apis/