概要
Ionicを使って開発をしていてFakeのJsonを読み込ましてデータ表示していたが、ちゃんとDB接続しなければいけないと思い立ってローカルにMongoDB立ててつなぐまでの道のりをメモします。
なお、最終的にはAWSのDynamoDBに接続させるリクエストをAngularのControllerに埋め込む予定なので、Node.js->MongoDBはあくまでテスト用で構築。
前提
開発環境は下記の通り。
- OS: Mac OS X El Capitan
- Angular: 1.X系
- Cordova CLI: 6.0.0
- Gulp version: CLI version 3.9.1
- Ionic Version: 1.2.4
- Ionic CLI Version: 1.7.14
- Ionic App Lib Version: 0.7.0
- Node Version: v5.5.0
- MongoDB shell version: 3.2.5
Angular -> MongoDB ?
そもそもフロントエンドのAngularとMongoを接続できるのか?というのでいろいろ調べてみた。
J2EE的に考えるとサーブレット(Web container)からDAOレイヤーに直繋ぎするものと考えるとできなくはないと思って調べたところ、やっぱり厳しそう(そもそもIonic動かすときに動いているサーバはNode.jsっぽいので、出来なくはないのかもしれない。けれど考慮事項が多くてめんどくさそうな印象を受けました)
ということで、代替案に移りました。
Ionic (Angular) -> Node -> MongoDB
素直に、間にNodeのサーバかまして通信したらいけるやろという思いで試行しました。
var express = require('express'),
mongodb = require('mongodb');
var app = express();
app.set('port', process.env.PORT || 3000);
// 私の環境ではMongoのポートである{ポート番号}=27017
// Nodeサーバのポート番号は3000としています
mongodb.MongoClient.connect("mongodb://localhost:{ポート番号}/{DB名称}", function(err, database) {
tmp = database.collection("{コレクション名称}");
});
// Acquiring the list of goods we bought
app.get("/api/tmp", function(req, res) {
tmp.find().toArray(function(err, items) {
res.send(items);
});
});
// サーバ起動
http.createServer(app).listen(app.get('port'), function() {
console.log('Node.js with Express server listening on port ' + app.get('port'));
});
app.jsを実行します。
$ node app.js
Node.js with Express server listening on port 3000
中身はシンプルなのでサーバは問題なく立ち上がります。
ionic側を実行
次にionic側を下記の通りコーディングします。
var url = 'http://localhost:3000/api/tmp';
$scope.tmplist = [];
$http.get(url).success(function(data, status) {
// set json data acquired from DB
console.info(status, data);
$scope.tmpList = data;
}).error(function(data, status) {
// if cannot get data from DB, set local json data to goodsList
console.error(status, data);
});
同じくionicを起動します。その後、Chromeをシークレットモードで立ち上げ、デベロッパーツールも合わせて起動させて確認します。
$ ionic serve ios
ブラウザから確認!
(ブラウザ検索)http://localhost:8100/
するとエラーが出てうまくいかない。。。ChromeのConsoleを見ると下記のエラーが出てました。
XMLHttpRequest cannot load http://localhost:3000/api/tmp. No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://localhost:8100' is therefore not allowed access.
・・・CORS(*1)か。たしかにMEANスタックでアプリケーション作るときはNodeとAngularは普通は同一ドメインにあるから気にしなくていいんよね。Angularで書いているから意識しなかったけど、Nodeサーバとionicサーバが別ドメインにあるから考慮せなあかんのか。。。となりました。
(*1) CORS:Cross-Origin Resource Sharingの略。ブラウザがHTMLを読み込んだサーバ以外のサーバからデータを取得する仕組みです。 一般的のブラウザには、XSS防止でクロスドメイン通信を拒否する仕組みが実装されています。
JSONPで対処してみる
CORS外しのヘッダーを入れてもいいけど、異なるドメインのJSONデータ取得はJSONPリクエストを投げるのが一般的のようなのでGETリクエストではなくJSONPリクエストを投げるように変更してみる。
<参考>
https://docs.angularjs.org/api/ng/service/$http#jsonp
http://qiita.com/izumin5210/items/e6e529d61c4d246f87fb
var url = 'http://localhost:3000/api/tmp?callback=JSON_CALLBACK';
$scope.tmplist = [];
$http.jsonp(url).success(function(data, status) {
// set json data acquired from DB
console.info(status, data);
$scope.tmpList = data;
}).error(function(data, status) {
// if cannot get data from DB, set local json data to goodsList
console.error(status, data);
});
再度ionicさんを実行してChromeからアクセス!!・・・あれ?うまくいかない。。。
てか変わらずCORSでてるやん。
※ブラウザから対象のurl1(http://localhost:3000/api/tmp)
にアクセスしても、ちゃんとJSON形式のデータ取れるは状態です。
いろいろJSONPの使い方を調べてapp.jsから返す形式がJSONになるように明示的に設定してみたりもしたけど変わらないので、次の作戦に移ります。
HTTPレスポンスにAccess-Controlなヘッダを付与する
CORS On Expressという記事を参考に組み込んでみる。
var express = require('express'),
mongodb = require('mongodb');
var app = express();
app.set('port', process.env.PORT || 3000);
app.use(function(req, res) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
});
・・・省略・・・
// Acquiring the list of goods we bought
app.get("/api/tmp", function(req, res) {
tmp.find().toArray(function(err, items) {
res.send(items);
});
});
nodeサーバ上げて、ionicコマンド叩いて確認した結果、CORSは発生しなくなったがなぜかHTTPレスポンスが一向にかえってこず画面レンダリングがされない。。。
下記の通り、明示的にヘッダに埋め込んでみるとうまくいった。
var express = require('express'),
mongodb = require('mongodb');
var app = express();
app.set('port', process.env.PORT || 3000);
・・・省略・・・
// Acquiring the list of goods we bought
app.get("/api/tmp", function(req, res) {
tmp.find().toArray(function(err, items) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
res.send(items);
});
});
この方法だと各リクエストに毎回CORS回避ヘッダを入れる必要が有るためにあまりよろしくはない気がするが、他の方法ではうまくいかなかったので断念。
とりあえずionicでMongoDBと通信させることには成功したということで満足。
なお、DynamoDB使うときはhttp.getのurlをAWS API GatewayかLambdaのURLにしたらOK。Node.jsのapp.jsに書いているCORS回避のヘッダを不要。