MongoDB」タグアーカイブ

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のほうが良さげであれば、本アプリを移植することも検討する。

express + mongodb でページャ機能を実装

前提

要素 バージョン
node 7.4.0
express 4.14.0
mongodb 3.2.9

概要

  • expressで動作しているAPIサーバに対して、mongodbに格納されているデータをリクエストする際に、ページを指定して取得する要素を絞り込む。
  • ページ番号はクライアントからサーバに対して送信する
  • 1ページあたりのデータ件数はサーバ側で固定する(今回は5件)
  • expressでmongodbを利用するための前準備などは割愛する

使用するmongodbのメソッド

メソッド名 説明
find(query) queryで指定したデータを取得する
skip(n) データを取得する際に先頭N件をスキップする
limit(n) データを取得する際に最大N件までとする

これらを組み合わせて以下のようにチェインすることでページングが実装できる

find(検索条件).skip((ページ番号 – 1) * 1ページあたりの件数).limit(1ページあたりの件数)

実装

今回は、Photoコレクションに含まれる全ての写真データを対象に、ページ番号のみをクライアントから指定して取得する

/* リクエストからページ番号を取得(デフォルト1) */
let page = Number(req.body.page) || 1;

/* 1ページあたりのデータ件数を5件にする */
let numPerPage = 5;

/* 全ての写真を取得 */
let c = collection('photo').find({});

/* 写真の枚数を取得 */
c.count().then(function(count) {

  /* 総ページ数を計算 */
  let pageNum = Math.ceil(count / numPerPage);

  /* 指定されたページ番号が総ページを上回っていた場合総ページ番号で置き換え */
  if (page > pageNum) { page = pageNum; }

  /* ページングを実行 */
  c.skip((page - 1) * numPerPage).limit(numPerPage).toArray(function(err, docs) {
    /* 写真一覧及びページング情報をクライアントに返却 */
    res.send({
      page: page,
      pageNum: pageNum,
      count: count,
      photo: docs,
    });
  });
});

所感

  • ページング処理とSPAの相性は抜群。僅かな待機時間もなくページを切り替えることができて気持ちいい
  • countとtoArrayで非同期処理が2回続いているので、Promissを使えばもっとキレイなコードになると思うが、Promissの使い方が上手く掴めなかったので今回は普通に入れ子を利用した

参考

MongoDBのよく使うコマンドメモ

初めてのMongoDBなので慣れるまではコレを見ながら一つ一つ丁寧に。

起動

$ mongo

終了

exit

データベースを一覧

show dbs();

データベースを選択(または新規作成)

use mydb;

コレクションの作成

db.createCollection(‘users’);

データベースのステータスを表示

db.status

データベースを削除

db.dropDatabase;

コレクションの一覧表示

show collections;

コレクション名を変更

db.users.renameCollection(“customers”);

コレクションを削除

db.users.drop();

コレクションにデータを挿入

db.users.insert({name: ‘sasaki , score: 70});

JavaScriptを用いてデータを挿入

db.users.insert({score: Math.random()});

コレクションのデータを全件表示

db.users.find();

コレクションのデータを全て削除

db.users.remove({});

コレクションを抽出(teamが1の要素)

db.users.find({team: 1});

コレクションを抽出(scoreが50以上)

db.users.find({score: {$gte: 50}});

コレクションを抽出(名前にsを含む)

db.users.find({score: /s/});

コレクションを抽出(名前にsを含みかつ80点以上)

db.users.find({name: /s/ , score: {$gte: 80}});

コレクションを抽出(名前にsを含むか、80点以上)

db.users.find($or , [{name: /s/} , {score , {$gte: 80}}]);

得点順にソート

db.users.find({}).sort({score: 1})

出力件数を制限

db.users.find({}).limit(3)

先頭N件はスキップ

db.users.find({}).skip(3)

1件のみデータを取得

db.users.findOne({})

特定データを更新

db.users.update({name: ‘sasaki’} , {$set , {score: 100}});

条件に当てはまる全てのデータを更新

db.users.update({} , {$set: {score: 100}} , {multi: 1});

ダンプを作成

$ mongodump -d [collection_name] --out [output_path]

ダンプからの復元

$ mongorestore --drop [dump_file_path]

参考

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アプリを描画する。そんな流れ。