Node.js」タグアーカイブ

MEANでデグー飼育アプリを作ってみた

概要

  • 前々からMEANスタックに興味があった
  • 2017/01/22ぐらいからチマチマMEANアプリ作り始めた
  • とりあえず自分にとって実用的であるのが望ましいので、デグー飼育支援アプリを作った
  • MEANだとこんなことができるよの紹介を含め、動作画面を掲載する
  • 完全に個人用なのでサービスの公開はしないが、ソースコードはGithubで公開している

システム構成

OS + MEAN構成は以下の通り

構成要素 バージョン
Debian 8.7
Mongodb 3.2.9
Express 4.14.0
AngularJS 1.4.1
NodeJS 7.4.0

その他、主に開発に用いたツールなどは以下の通り

構成要素 バージョン
npm 4.1.2
babel 6.22.2
eslint 3.0.1
less 3.3.0
gulp 3.9.1
jQuery 3.1.1
jQUeryUI 1.8.19
bootstrap 3.3.7
d3.js 3.5.16
c3.js 0.4.1

ソースコード

https://github.com/Sa2Knight/degu-log よりクローン可能。

環境構築手順も一応READMEにあるので、頑張ればローカルでも動作させられる(多分)
Zaimアカウント及びZaimAPIを利用するためのトークンが必要だったり敷居は高い。

機能一覧

高速な画面遷移

初っ端から機能というより特徴の話だが、本アプリはSPA(っぽい何か)でありながら、URLを変更しながら画面遷移を行える。
そのため、ブラウザの「戻る」「進む」も利用可能であり、URLを直接指定して特定のリソースにアクセスすることもできる。

AngularJSのオプションモジュールである、ng-routeを用いることで、それを簡単に実装できた。
しかも画面遷移が非常に早い。これだけでMEANで開発してよかったと思えるレベル。

簡易ブログ機能

飼育に関する簡易的なブログっぽいものを投稿することができる。
ブログは通常のCRUDの他、カレンダーを表示して記事を一覧することができる。

体重管理機能

デグーの体重を時系列で管理することができる。便宜上、我が家のデグー2匹(パズー、メイ)に特化しており、それぞれ日付に対する体重を記録することができる。
また、時系列に記録した体重を元に、折れ線グラフをリアルタイムで描画することができる。

ペット関連支出管理機能

本機能は、クラウド家計簿サービスであるZaim及び入力した家計簿記録を取得するZaimAPIを用いて実装している。
日頃から入力している家計簿データを元に、月ごとの支出額の一覧及び特定月の支払内容を閲覧できる。

外部APIの利用もやはりSPAとの相性が抜群だ

写真管理機能

写真をアップロードして管理することができる。写真には、タイトル及び1つ以上のタグを付与することができ、それらで検索することができる。
また、写真アップロード時にオリジナルと別にサムネイルを生成し、写真一覧画面にはサムネイルが表示され、クリックすることでオリジナルサイズを確認できるようにアンっている。

所感

MEANという、MもEもAもNもほぼ使ったこと無い状態から、1つずつ勉強しながらなんとか実用性も多少あるWebアプリを作ることができた。特にSPAを1から作るのは初めての経験だったので、従来型のWebアプリとの違いを感じながら楽しく開発することができた。以下、主な所感を列挙する

  • MongoDBはJavaScriptライクに読み書きを行え、スキーマに依存せずにデータを管理できるので手軽で楽しい
  • AngularJSは双方向データバインディングが一番強いと思ったが、フルスタックのMVCであればAngularJSでなくても良いかなとは思う。
  • Nodeを使えばだいたいのことは対応するNodeモジュールがあって、サーバサイドを一通りJavaScriptで書ける。強い。
  • Expressについては、今回はRestfulなAPIサーバとしてしか使わなかったので触り程度しかわからなかった
  • SPAはクライアントの待機時間が極端に短くなるのでストレス無く利用できることを強く実感
  • クライアントサイドの負担を増やし、サーバサイドの負担を減らす新しい分散システムだと感じた
  • SPAでも画面遷移ごとにURLを変化させたほうが絶対便利だと思った
  • サーバサイドはRestfulなAPIに特化させるように実装したほうが分担が明確で良い
  • JavaScriptはBabelを導入して、全てES6で記述した。やはり最新仕様のJavaScriptは書きやすい。もう戻りたくない。
  • ESLintを導入してコーディング規約を強制した。おかげで比較的高品質のコードを終始簡単に弄るすことができた
  • Bootstrapに頼りきりだった割にUIが雑で残念
  • 一応LESSも導入したが特に使いこなせてない
  • テスト(ユニット,E2E)を書きたかったが、その余裕がなかった。そのうち書きたい
  • とにかく新しい技術/ツールで溢れた開発で楽しい1ヶ月半だった

次はTypeScript及びAngular2に手を付けてみたい。Angular2のほうが良さげであれば、本アプリを移植することも検討する。

Zaim.jsをAPI ver2.xが使えるように改良してみた

前提

  • NodeによるZaimAPIの利用 の続き
  • 内部を書き換えるだけなので、基本的な使い方は上記記事と同様
  • 元のモジュール(Zaim.js)がMITライセンスなのでそれに準ずる

概要

npmでインストールできる、zaim.jsは、Zaim API ver1を前提としているため、ver2の機能を利用することができない。
そこで、zaim.jsのソースコードをベースに、API ver2を利用できるように改変する。

OAuth用URLの差し替え

OAuthで認証してzaimオブジェクトを作成する部分の、認証用URLをver2のURLに差し替え

変更前

this.client = new oauth.OAuth(
  'https://api.zaim.net/v1/auth/request',
  'https://api.zaim.net/v1/auth/access',
  this.consumerKey,
  this.consumerSecret,
  '1.0',
  this.callback,
  "HMAC-SHA1"
);

変更後

this.client = new oauth.OAuth(
  'https://api.zaim.net/v2/auth/request',
  'https://api.zaim.net/v2/auth/access',
  this.consumerKey,
  this.consumerSecret,
  '1.0',
  this.callback,
  "HMAC-SHA1"
);

リクエストトークン取得用のURLを差し替える

変更前

this.client.getOAuthRequestToken(function(err, token, secret) {
  that.token = token;
  that.secret = secret;
  callback('https://www.zaim.net/users/auth?oauth_token=' + that.token);
});

変更後

this.client.getOAuthRequestToken(function(err, token, secret) {
  that.token = token;
  that.secret = secret;
  callback('https://auth.zaim.net/users/auth?oauth_token=' + that.token);
});

入力データ取得用APIのURLを差し替える

ver1ではPOSTだったが、ver2ではGETになるため、それについても書き換える
変更前

Zaim.prototype.getMoney = function(params, callback) {
  var url = "https://api.zaim.net/v1/money/index.json";
  if (arguments.length === 1) {
    callback = params;
    params = {};
  }
  this._httpPost(url, params, callback);
};

変更後

Zaim.prototype.getMoney = function(params, callback) {
  var url = "https://api.zaim.net/v2/home/money";
  if (arguments.length === 1) {
    callback = params;
    params = {};
  }
  this._httpGet(url, params, callback);
};

httpGetメソッドで、パラメータを付与できるように改変する

オリジナルのhttpGetメソッドは、パラメータの付与に対応していなかったので、対応できるように修正する
変更前

Zaim.prototype._httpGet = function(url, callback) {
  if (!this.token || !this.secret) {
    throw new Error("accessToken and tokenSecret must be configured.");
  }
  this.client.get(url, this.token, this.secret, function(err, data) {
    if (err) {
      throw err;
    } else {
      callback(typeof data === 'string' ? JSON.parse(data) : data);
    }
  });
};

変更後

Zaim.prototype._httpGet = function(url, params , callback) {
  url += '?';
  Object.keys(params).forEach(function(key) {
    url += key + '=' + params[key] + '&';
  });
  if (!this.token || !this.secret) {
    throw new Error("accessToken and tokenSecret must be configured.");
  }
  this.client.get(url, this.token, this.secret, function(err, data) {
    if (err) {
      throw err;
    } else {
      callback(typeof data === 'string' ? JSON.parse(data) : data);
    }
  });
};

Zaim API ver2特有の機能を使ってみる

ここまでで、getMoneyメソッドに関してはver2のAPIを呼び出せるようになったので、ver2でしか利用できない機能を使ってみる。
下記コードは、2017年にAmazonで購入した商品の一覧を取得する。

ソースコード

zaim.getMoney({
  place: 'Amazon',
  start_date: '2017-01-01'
} , function(data) {
  console.log(data.money.map(function(m) {
    return [m.date , m.place , m.amount , m.comment];
  }));
});

実行結果

$ node zaim.js
[ [ '2017-02-12', 'Amazon', 1976, 'コーディングを支える技術' ],
  [ '2017-02-04', 'amazon', 2597, 'かじり木、チモシー、ペレット、消臭、砂' ],
  [ '2017-02-04', 'Amazon', 702, 'リンゴ酢' ],
  [ '2017-02-01', 'Amazon', 980, 'Kindle Unlimited 201702' ],
  [ '2017-01-14', 'Amazon', 1734, '野菜ジュース12本セット' ],
  [ '2017-01-09', 'Amazon', 258, '応用情報技術者の本中古' ],
  [ '2017-01-04', 'Amazon', 2880, 'データベーススペシャリスト教科書' ],
  [ '2017-01-01', 'Amazon', 18820, 'FIRE HDとそのカバーとその保証' ],
  [ '2017-01-01', 'Amazon', 980, 'Kindle Unlimited 201701' ] ]
$

購入店舗に関する情報はver2から追加された機能で、ver1ではできなかった、店舗によるフィルタリングができたことがわかる。また、ver1では支出額は負数だったが、ver2では収支に関わらず正数となっている。

また、上記実行結果には関係ないが、ver1では最大取得件数が100件だったのに対し、ver2では無制限となっており汎用性も高まっている。

他のメソッドについて

同様に他のメソッドについても、APIのURLをver1のURLからver2のURLに差し替えることで使用可能となるが、
個人的にgetMoneyメソッドさえ使えれば今は困らないのでコレ以上の改変は必要に応じて行うことにする。

参考

NodeによるZaimAPIの利用 と同様

NodeによるZaimAPIの利用

本記事で使用したZaim.jsは、最終更新が2013年で、最新のAPIを利用することができません。そのため、Zaim.jsのソースコードを直接編集し、最新のAPIを利用できるように書き換えたものを利用しています。

書き換えについては、Zaim.jsをAPI ver2.xが使えるように改良してみたをご参考ください

前提

  • node/npmが使える状態
  • Zaimのアカウントを保有しており、最低限の利用記録がある状態
  • Zaim API用のカスタマーキー、アクセストークンを何らかの手段(OAuth)で取得している状態

かなり限定的な状態が対象なので限りなく個人備忘録用

概要

クラウド型の家計簿サービスであるZaimを、Node上でAPI経由で読み書きする。
本記事ではZaim API用のカスタマーキー及びアクセストークンを取得済みで、APIを利用できる状態にあることを前提とする

Zaimとは

Zaimは、おそらく国内シェアナンバー1のクラウド型家計簿サービスであり、Web/iOS/Androidそれぞれから利用することができる。他の家計簿ソフトと同様に、会計簿の記入、集計、検索、予算管理など、家計簿サービスとしての基本的な機能を備えている他、以下の特徴的な機能を持っている。

  • レシートを撮影することで全自動で入力が行われる。精度はお察し
  • 各金融機関と連携し、入出金を自動入力。金融機関の認証情報を民間企業に渡すとかどうなんだろ
  • 年末調整とかそれなりに自動でやってくれる。正確に家計簿を記入していないと無意味

などと特徴的な機能はあんまり使いものにならないので、私は普通に家計簿の入出力にしか使っていない。(※上記は私的意見です)

Zaim API とは

OAuthにてZaimのアカウントの認証を受け、API経由で家計簿のCRUDを行うことができる。

Zaim APIはRESTFULLに実装されているので、そのまま利用することも簡単であるが、今回はZaim APIをラッピングしたNodeモジュールであるZaim.jsを利用する。

Zaim.jsのインストール

Nodeモジュールなのでnpmを用いてインストール

npm install zaim -D

Zaimオブジェクトの作成

なんらかの手段で取得したカスタマーキーとアクセスキーを指定して、Zaimオブジェクトを作成する

var Zaim = require('zaim');

var zaim = new Zaim({
  consumerKey: 'hogehoge',
  consumerSecret: 'fugafuga',
  accessToken: 'foofoo',
  accessTokenSecret: 'barbar',
});

とりあえず入力履歴を取得

getMoneyメソッドで入力履歴を取得。デフォルトでは最新20件を取得するので、とりあえず2/17の記録を全て取得する。
ちょっと恥ずかしいが、実行結果は生データ。

ソースコード

zaim.getMoney({
  start_date: '2017-02-17',
  end_date: '2017-02-17'
} , function(data , err) {
  console.log(data.money);
});

実行結果

$ node zaim.js
[ { id: 1581817347,
    user_id: 3061609,
    date: '2017-02-17',
    category_id: '101',
    genre_id: '10105',
    comment: 'サラダ、ドレッシング、卵とじ丼',
    name: '',
    active: 1,
    created: '2017-02-17 19:43:22',
    currency_code: 'JPY',
    type: 'pay',
    price: '-341' },
  { id: 1581291695,
    user_id: 3061609,
    date: '2017-02-17',
    category_id: '101',
    genre_id: '10104',
    comment: '月見大盛り',
    name: '',
    active: 1,
    created: '2017-02-17 14:06:50',
    currency_code: 'JPY',
    type: 'pay',
    price: '-350' } ]

入力データの構成

前項では、2/17の記録2件の入力データを取得した。入力データはオブジェクト形式で取得でき、以下の構成になっている。

キー 内容
id 入力ID(ユニーク)
user_id ユーザID(ユニーク)
date 記録日時
category_id カテゴリ(食費/日用雑貨などの大分類)のID
genre_id ジャンル(朝食/昼食/夕食などの小分類)のID
comment 記録に対する自由記入欄(購入したモノなどを記入する)
name 不明(外部連携とかで使う??)
active 多分論理削除すると0になる
created データの登録日
currency_code お金の種類
type 支出(pay) or 収入(income)
price 金額(支出の場合負数)

特定の入力データを取得する

ここでは、2016年に「ゲーム」で使った金額の一覧を取得する

ジャンル一覧を取得

Zaimでは、「ゲーム」は、「エンターテイメント」カテゴリに属するジャンルとしてデフォルトで登録されている。
Zaim.jsでは、genre_idで指定する必要があるので、「ゲーム」のジャンルIDがいくつなのかを調べるために、以下のコードで支出のジャンル一覧を取得する。

ソースコード

zaim.getPayGenres({
  lang: 'ja',
} , function(data) {
  console.log(data);

実行結果

$ node zaim.js
{ genres:
   [ { id: 10101, category_id: 101, title: '食料品' },
     { id: 10102, category_id: 101, title: 'カフェ' },
     { id: 10103, category_id: 101, title: '朝ご飯' },
     { id: 10104, category_id: 101, title: '昼ご飯' },
     { id: 10105, category_id: 101, title: '晩ご飯' },
     { id: 10199, category_id: 101, title: 'その他' },
(中略)
     { id: 9047786, category_id: 108, title: 'ゲーム' },
(以下省略)

ゲームの支出履歴を取得

「ゲーム」のジャンルIDが9047786であることがわかったので、これを指定して2016年のゲームの支出一覧を取得する。
全部出力してしまうと長いので、価格とコメントのみmapして出力する。

ソースコード

zaim.getMoney({
  start_date: '2016-01-01',
  end_date: '2016-12-31',
  genre_id: '9047786',
} , function(data , err) {
  console.log(data.money.map(function(m) { return [m.price , m.comment]}));
});

実行結果

$ node zaim.js
[ [ '-1080', '桃鉄' ],
  [ '-1200', 'ポケモンGO' ],
  [ '-1200', 'ポケモンGO' ],
  [ '-1200', 'ポケモンGO' ],
  [ '-600', 'ポケモンGO' ],
  [ '-600', 'ポケモンGO' ],
  [ '-600', 'ポケモンGO' ],
  [ '-950', 'ポケモン空の探検隊' ],
  [ '-706', 'マジカルバケーション' ] ]

ポケモンGOにほどよく課金していたのがわかる。

参考

ESLintによるJavaScriptコーディングルールの強制

前提

要素 バージョン 関連記事
node/npm 7.4.0/4.1.2 Debian8.7にMEAN開発環境を構築する
gulp 3.9.1 Babel+Gulpで始めるES6

概要

構文チェックツール”ESLint”を用いて、JavaScriptのコーディングルールを強制し、コード品質を高める。

また、gulpによってESLintの自動実行することで、リアルタイムにコードを検証する。

JavaScript構文チェックツールについて

JavaScript構文チェックツールとは、JavaScriptコード中に潜む、シンタックスエラーやバグの元となりえる構文を静的に解析して通知してくれるツールのこと。

例えば以下のJavaScriptコードは、JavaScriptの仕様上では正常に動作する。

var hoge = 3;
var fuga = function(a , b , a) {
console.log(a + b);
}
fuga(1 , 2 , 3)

しかし、上記コードは以下の問題を含んでいる

  • 変数hogeは使用されていない(1行目)
  • 関数fugaに使用されていない第三引数がある(2行目)
  • 関数fugaの第一引数と第三引数の名前が重複している(2行目)
  • インデントされるべき行でされていない(3行目)
  • セミコロンが挿入されていない(5行目)

このような、実行時のエラーとはならないが、潜在的なバグの原因となったり、可読性を損ねるようなコードが含まれている場合にそれを通知するのが構文チェックツールの役割だ。

ESLintについて

ESLintは、他の構文チェックツールと比べて以下の特徴を持っている

  • 全ての検証ルールについて、検証のON/OFFを選択できる
  • 独自の検証ルールを作成することができる
  • ルールごとのドキュメントが充実している

今回はNode環境上にESLintを導入し、推奨の設定に加えていくつかの検証ルールを追加でONにすることにする。

ESLintのインストール

本記事では、gulp上でESLintを利用するので、gulp-eslintをnpmでインストールすることで、依存先のeslintもまとめてインストールする。

npm install -D gulp-eslint

.eslintrc.jsonの作成

eslintの設定は、隠しファイルである.eslintrc.jsonに記述する。今回作成したeslintrcを以下に示す。

{
    "extends": ["eslint:recommended"],
    "plugins": [],
    "parserOptions": {},
    "env": {
      "browser": true,
      "es6": true
    },
    "globals": {},
    "rules": {
      "no-var": "error",
      "semi": "error",
      "block-spacing": "error",
      "indent": ["error" , 2],
      "no-mixed-spaces-and-tabs": "error",
      "no-multiple-empty-lines": ["error" , {"max": 1}],
      "no-trailing-spaces": "error",
      "space-infix-ops": "error",
      "dot-notation": "error",
      "eqeqeq": "error",
      "no-else-return": "error",
      "no-loop-func": "error",
      "arrow-parens": "error",
      "arrow-spacing": "error",
      "no-console": "warn",
      "no-undef": "off"
    }
}

eslintrcの主な構成は以下の通り

  • extendsでベースとなる設定を指定し、ここではeslintの推奨設定を継承する(2行目)
  • eslintの対象コードの実行環境を指定し、ここではブラウザ上で動作し、ES6を利用していることを記述する(5行目)
  • 追加ルールを指定する。ベースとなる推奨設定に含まれるルールを記述する必要はない(10行目)

ruleは{ルール名: 設定}の形式で記述する。基本的に設定は、以下の3択で指定し、場合によっては追加設定情報を配列で与える。

  • “error”: ルールに反している場合に警告を出力する(強調)
  • “warn”: ルールに反している場合に警告を出力する(控えめ)
  • “off”: 警告を出力しない

上記eslintrcに含まれるルールは以下の通り

ルール名 ルール内容 異常系 正常系
no-var varの使用を禁止 var hoge let hoge
semi セミコロンの省略を禁止 let hoge let hoge;
block-spacing 単一行ブロックで内側スペースの挿入を強制 if(hoge) {fuga();} if(hoge) { fuga(); }
indent(2) インデントをスペース2字の強制
no-mixed-spaces-and-tabs タブとスペースのインデントの混在を禁止
no-multiple-empty-lines(1) 2行以上連続した空行を禁止
no-trailing-spaces 行の末尾へのスペースの付与を禁止
space-infix-ops 演算子の前後にスペースを強制 a=b+c; a = b + c;
dot-notation ドット表記可能な場合にドット表記を強制 hoge[‘fuga’] hoge.fuga
eqeqeq ==の使用を禁止 a == b a === b
no-else-return else文での不要なreturnを禁止
no-loop-func ループ内で関数を定義することを禁止
arrow-parens アロー関数の特定フォーマットを強制 (a) => a * 2; a => a * 2
arrow-spacing アロー関数の矢印の前後にスペースを強制 a=>a * 2; a => a * 2
no-console console.logの禁止
no-undef 宣言されていない変数の使用を禁止

※ no-consoleはデバッグでは使用したいため、errorではなくwarningに設定
※ no-undefは、外部ライブラリで宣言された変数を利用することがあるのでoffに設定

gulpfileを編集

var gulp = require('gulp'),
    eslint = require('gulp-eslint');

gulp.task('eslint' , function() {
  return gulp.src('app/scripts/*.js')
    .pipe(eslint())
    .pipe(eslint.format())
});

gulp.task('watch' , function() {
  gulp.watch('app/scripts/*.js' , ['eslint']);
});

gulp.task('default' , ['babel' , 'eslint' , 'watch']);

上記gulpfileにて、gulp実行時及びapp/scripts/*.jsに変更が生じた場合に、eslintを実行し、errorかwarningが発生した場合に警告を出力する。

デモ

画面左側でJavaScriptのコーディングを行い、画面右側でESLintを実行するgulpが動いている。
デモ内では、初めに「varの使用を禁止」「セミコロンの使用を強制」「参照されない変数の宣言禁止」に引っかかるコードを記述し、ESLintに指摘を受けながらそれを修正している


(クリックで拡大)

所感

  • 自分の中でコーディングルールを定めでも、それを実行できてないことも少なくない。ESLintを導入すると大リーグボール養成ギブスを付けているかのような感覚でコーディングすることになり、初めは息苦しくも感じるが、それが自然とできるようになってくると、コーディングスタイルの一貫性が取れるようになり良いコードを無意識で書けるようになると思う。
  • 例によってgulpとの相性は最高に良い。デモのように画面を分割してリアルタイムで検証できるのは、まるで統合開発環境を使ってるようで気持ちが良い
  • 今回始めてgifアニメーションを撮ってみたが、結果的にテイク10ぐらいかかった。何故録画してるとタイポが増えるのだろうか。撮影は大変だが、百聞は一見に如かずなので今後も無駄にテキスト増やすよりもデモを用意するようにしよう

参考

Debian8.7にMEAN開発環境を構築する

概要


MEANは近代的でフルスタックな開発フレームワーク。以下の構成要素の頭文字をつなげたもの

M MongoDB NoSQLなデータベース
E Express Node上で動くWebアプリケーションフレームワーク
A AnglarJS フロントエンドのMVCフレームワーク
N NodeJS サーバサイドのJavaSciprt実行環境(Webサーバの役割)

今回は、vagrantを用いて以下のように仮想環境を立ち上げて、そこにMEAN環境を構築した

$ vagrant box add debian https://github.com/holms/vagrant-jessie-box/releases/download/Jessie-v0.1/Debian-jessie-amd64-netboot.box

導入したOSは以下のとおり、Debian 8.7である

$ cat /etc/debian_version
8.7
$ uname -a
Linux Debian-jessie-amd64-netboot 3.16.0-4-amd64 #1 SMP Debian 3.16.39-1 (2016-12-30) x86_64 GNU/Linux

MongoDB


MongoDBはNoSQL(表形式じゃないDBMS)のデファクトスタンダード。

MongoDBのインストール

$ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 7F0CEB10
$ echo "deb http://repo.mongodb.org/apt/ubuntu "$(lsb_release -sc)"/mongodb-org/3.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-3.0.list
$ sudo apt-get update
$ sudo apt-get install mongodb-org

適当にデータを入れておく

ここでは、mydbというデータベースを作成し、さらにUsersというコレクション(テーブルのようなもの)を作成し、そこに適当なオブジェクトを挿入する。

$ mongo
(省略)
> use mydb;
> db.users.insert({name: 'sasaki' , age: 24});
WriteResult({ "nInserted" : 1 })
> db.users.find({});
{ "_id" : ObjectId("5881d7ed4393e91ab4ebd18d"), "name" : "sasaki", "age" : 24 }

バイナリをダウンロード

何故か上記のやり方でバイナリが落ちなかったので別途入れる

$ curl -O https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-3.2.9.tgz
$ tar -zxvf mongodb-linux-x86_64-3.2.9.tgz
$ mv mongodb-linux-x86_64-3.2.9/bin/* /usr/local/bin

確認

$ mongo --version
MongoDB shell version: 3.2.9

NodeJS

NodeJSはサーバサイドでのJavaScriptの実行環境。ノンブロッキングIOを用いて大量アクセスを華麗に裁く

nodejsのインストール

aptを用いてインストールできるnodeは非常に古い。
とりあえず古いバージョンをインストールし、バージョンアップする手段を用いる。

$ sudo apt install nodejs
$ nodejs -v
v0.10.25

npmのインストール

npmはnodeで動くパッケージの管理ツール。必須。

$ sudo apt install npm
$ npm -v
4.1.2

nのインストール

nはnode本体のバージョンを管理するツール。npmを用いてインストールする

$ sudo npm -g install n
$ n --version
2.1.3

nodeを最新版に引き上げる

$ sudo n stable
$ node -v
v7.4.0

Express


expressはnode上で動作するWebアプリケーションフレームワーク。これもnpmを用いてインストールする

expressのインストール

$ npm install express -g
$ express --version
4.14.0

AngularJS


AngularJSはフロントエンドのMVCフレームワーク。こちらはCDNを用いるので、別途インストールの作業はない。

Hello,World


必要なnodeモジュールを追加でインストールする

ejsはテンプレートエンジン。mongodbはnodeからmongodbにアクセスするモジュール。

$ npm install ejs
$ npm install mongodb

nodeでmongodbに接続するためのスクリプトを作成

ここでは27017番ポートでmongodbが立ち上がっているものとし、データベース名がmydbであるとする。

var db;
var MongoClient = require('mongodb').MongoClient;
var assert = require('assert');
var url = 'mongodb://0.0.0.0:27017/mydb';

MongoClient.connect(url, function(err, mongodb) {
  assert.equal(null, err);
  console.log("Connected correctly to server");
  db = mongodb;
});

var collection = function(name) {
  return db.collection(name);
}

module.exports = collection;

メインスクリプトを作成

作業ディレクトリにapp.jsを作成する。この例では、8086番ポートでサーバを立ち上げている。

// express準備
var express = require('express');
var ObjectID = require('mongodb').ObjectID;
var collection = require('./mongo');
var COL = 'users';
var app = express();

// テンプレートディレクトリのパスを指定
app.set('views', __dirname + '/views');

// テンプレートエンジンをejsに指定
app.set('view engine', 'ejs');

// ルーティング設定
app.get('/', function (req, res) {
  collection(COL).find({}).toArray(function(err , docs) {
    res.render('index' , {name: docs[0].name});
  });
  //res.render('index');
});

// サーバー起動
var server = app.listen(8086, function () {
  var host = server.address().address;
  var port = server.address().port;
  console.log('Example app listening at http://%s:%s', host, port);
});

トップページを作成

views/index.ejsを作成する。この例では、AngularJS1.6.1をCDNから引っ張ってきている。
ややこしいが、<%= hoge %>が、ejsによるサーバサイドでの値出力。{{hoge}}が、AngularJSによるクライアントサイドでの値出力となる。

<!doctype html>
<html ng-app>
  <head>
    <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.1/angular.min.js"></script>
  </head>
  <body>
    <div>
      <label>Name:</label>
      <input type="text" ng-model="yourName" placeholder="Enter a name here">
      <hr>
      <h1>Hello {{yourName}}! My name is <%= name %>!</h1>
    </div>
  </body>
</html>

サーバを起動

$ node app.js
Example app listening at http://:::8086

ブラウザから8086番ポートにアクセスし、以下の画面が表示されれば成功

クライアントからの要求は、NodeJSがリクエストを初めに受け取る。NodeJS上で動作するexpressは、URLを元にルーティングし、MongoDBからデータを取得してejsテンプレートに引き渡している。ejsがテンプレートに値を埋め込むと、それをHTMLとしてクライアントに返却。クライアントはHTMLに含まれているAngularJSをダウンロードし、Angularアプリを描画する。そんな流れ。