Node.js(restify) + MongoDB(mongoose) でAPIサーバつくってHerokuでデプロイするまで
先日ようやくリリースしたアプリ「お階段登り」では、ただ階段を上るだけのクソアプリと思いきや、他のユーザーの位置まで来たらすれ違ってみたり、一番上にいる人の段数をとってきたり、影でコソコソやっております。(クソなのには変わりませんが。)
https://itunes.apple.com/jp/app/o-jie-duan-dengri-zuo-rino/id956171342?mt=8&uo=4&at=10l8JW&ct=hatenablog
コソコソやるためにサーバーを自作しました。
サーバー側でやってること例
- ユーザー登録
- 自分のスコアを更新する
- 1位のスコアを取得する
- 自分が1位かどうか返す
- 自分のスコアに一番近いユーザの情報を返す(→ユーザのスコアまで到達したらすれ違い実行)
アプリからはHTTPリクエスト投げ、JSON形式でレスを取得してレスによって描画を変えたりします。
こんなAPIサーバー的なものをNode.jsをつかって実装しました。
備忘録でサーバー作成からHerokuへのデプロイまでやり方を残します。
環境
- Mac OS 10.10.2
- Node.js v0.10.25
- MongoDB 2.6.7
事前に勉強したこと
本件の記事と直接関係ないので、別記事にします。
↓こちら
Node.js + MongoDBでAPIサーバつくるまでに勉強したこと - イッツァハローワールド
なんでわざわざRestifyつかったか。
ググったら「REST Webサービスの構築に特化したフレームワーク」って出てきたのでおおそうかつかってみよ!って感じで使いました。
今回つくるような簡単なもんならExpressで十分に思えます。(作った後でわかりましたw)
あと、英語のドキュメントで四苦八苦してみたかったっていうのもちょこっとあります。
まずはRestifyをつかって簡単なサンプルコードをつくる
Node.js入れるところとかは割愛します。
restifyをインストール
$ npm install restify
サンプルコード実装
以下は、http://0.0.0.0:8080/hello_worldにアクセスするとmessage : Hello World!って内容のJSONを返す例。
// sample.js var restify = require('restify'); // サーバー生成 var server = restify.createServer(); // http://0.0.0.0:8080/hello_worldにGETリクエストしたときの処理 function helloWorld(req, res, next){ // レスポンス res.send({message: 'Hello World!'}); } // パスと関数の紐付け // ↓はhttp://0.0.0.0:8080/hello_worldにGETリクエストしたらhelloWorld関数を実行 server.get('/hello_world', helloWorld); server.listen((8080), function() { console.log('%s listening at %s', server.name, server.url); });
実行
コマンド叩いて実行。
$ node sample.js
http://0.0.0.0:8080/hello_worldを開いてみる
できた。こんなかんじで、APIごとに関数と、関数とパスとの紐付けを定義すればわりと簡単に作れます。
以下はmongooseを落として、MongoDBをつかってCRUDします。
実際のAPIサーバ実装やってみる
まずMongoDB, mongoose入れる
$ npm install mongodb $ npm install mongoose
コード
// sample.js var restify = require('restify'); var server = restify.createServer(); // サーバー生成 server.use(restify.bodyParser()); // POSTデータをパースするひとを登録 server.use(restify.queryParser()); // クエリをパースするひとを登録 var mongoose = require('mongoose'); var uristring = 'mongodb://localhost/'; var db = mongoose.connect(uristring); var Schema = mongoose.Schema; // スキーマの定義 var userSchema = new Schema({ score: Number, }); // モデルを生成 mongoose.model('user', userSchema); var User = mongoose.model('user'); // Create function createUser(req, res, next){ var user = new User(); // コレクションを作成 user.score = 0; // つくったコレクションを保存 user.save(function(err, data){ res.send({function:'Create', status: 'OK', id: data._id}); }); } // Read function getUsers(req, res, next){ User.find(function(arr, data){ // すべてのコレクションの情報を返す res.send(data); }); } // Delete function deleteUser(req, res, next){ // 入力されたIDのコレクションを削除する User.remove({ _id: req.params.id }, function(err){ res.send({function:'Delete', status: 'OK'}) }); return next(); } function updateScore(req, res, next){ // IDを検索 User.findById(req.params.id, function (err, data) { // エラー処理 if (err) { res.send({function:'Update', status:'NG', reason: 'User not found.'}); return next(); }; // 入力パラメータ異常 if (req.params.id == '' || req.params.id === undefined ) { res.send({function:'Update', status:'NG', reason: 'User not found.'}); return next(); }; if (req.params.score == '' || req.params.score == 0) { res.send({function:'Update', status:'NG', reason: 'Score is invalid.'}); return next(); }; // 入力されたIDのスコアを入力されたものに変更する User.update({ _id: req.params.id }, { $set: {score: req.params.score} }, { upsert: false, multi: true }, function(err) { if (err) { res.send({function:'Update', status:'NG', reason: 'Update faild'}); return next(); }; res.send({function:'Update', status:'OK'}); }); }); } server.get('/get_users', getUsers); server.post('/delete_user', deleteUser); server.put('/update_score',updateScore); server.post('/create_user',createUser); server.listen((8080), function() { console.log('%s listening at %s', server.name, server.url); });
動作確認
実際に各API呼んだらどんなレスポンスを返すか見てみます。
動作を見るためにChromeのエクステンションのAdvanced REST Clientをつかいます。
create(create_user)
まずはコレクションを生成してみます。
Advanced REST Clientを起動し、Reqestタブを開きます。
POSTを選択し、パスを入力します。
入力したら「Send」ボタンを押下するとレスポンスが帰ってきます。
これでひとつコレクションができました。
get(get_users)
つぎに生成したコレクションを取得します。
こんどはGETを選択します。
「Send」ボタン押下で、先ほど作ったコレクションが取得できます。
update(update_score)
コレクションのscoreを更新します。
こんどはPUTを選択し、更新するid, 更新後のscoreを入力します。
ためしに1234を入れてみます。
「Send」ボタン押下で、更新完了が帰ってきます。
再度GETしてスコアを見てみます。
更新されました。
入力エラー用のコードも書いてみたので動作を確認してみます。
ためしに、idを存在しない値に書き換えて「Send」ボタンを押下すると
エラーが帰ってきました。
delete(delete_user)
つくったコレクションの削除を行います。
POSTを選択して、削除したいコレクションのidを入力します。
「Send」ボタン押下でコレクションが削除されます。
GETして確かめてみます。
DBが空になってました。
やった!
Herokuへデプロイしてみる
ここまでで実装はほぼ終わり。Herokuへデプロイしてみます。
Heroku toolbeld(Heroku推奨)のインストール
Herokuにログイン
$ heroku login
Herokuへ登録した時のメールアドレスとパスワードを聞かれるので入力します。
package.json作成
適当に入力します。
$ npm init
今回作成したのはこんな感じです。
{ "name": "sample", "version": "0.0.1", "description": "sample api server", "main": "sample.js", "dependencies": { "mongodb": "~1.4.33", "mongoose": "~3.8.24", "restify": "~2.8.5" }, "devDependencies": {}, "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "author": "hanamiju", "license": "MIT" }
Heroku用のProcfileを作成
web: node sample
こやつにHerokuから呼ぶjsファイルを記載します。拡張子はいらないです。
Herokuのプロジェクト作成
プロジェクトを生成する。今回はapiserverkunnという名前で登録しました。
$ heroku create apiserverkunn
MongoLabをいれる
先ほどまではローカル環境にMongoDBを構築してましたが、デプロイにあたりMongoLabを入れます。
MongoLabはHerokuのアドオンのひとつで、HerokuでMongoDBを496MBまで無料で使えます。最高。
というわけでアドオンを探します。Mongoとか検索ワードで入れればでてきます。
こいつのfreeプランをapiserverkunnに適用させます。
上記のようにブラウザからでもいいし、
$ heroku addons:add mongolab
でもオーライ。
sample.jsファイルを修正する。
sample.jsはこのままだとローカル環境でしか動かないので、ファイルを一部修正します。
まずはMongoDBとの接続時のパス
var uristring = 'mongodb://localhost/'; // 変更前 var uristring = process.env.MONGOLAB_URI || 'mongodb://localhost/'; // 変更後
これで、process.env.MONGOLAB_URIに接続し、存在しない場合はmongodb://localhost/に接続される。
つまり、ローカルと本番でコードを改変せずに済むということ。
つぎにサーバー処理開始時
server.listen((8080), function() { // 変更前 server.listen((process.env.PORT || 8080), function() { // 変更後
簡単に実行環境毎の設定でアプリケーションを実行することができます。
Herokuへデプロイ
$ git init $ git add . $ git commit -m "hoge" $ git push heroku master
結果を見てみる
あらかじめcreateしといてから、getしてレスポンスを見てみます。
ちゃんと表示されました。ばんざい!