node.js: mongoDBに繋げて日付で検索

JavaScriptで日時の取り扱いって本当に面倒くさいですよね。
node.jsとmongoDBで日時を指定して検索したい時、単純に

col.find({ "datetime": ISODate("2018-12-11T04:31:09Z") });

と書いても動いてくれません。

日時をISO形式のobjectで渡せば動くのですが、んじゃISO形式のobjectで渡すのってどうやるんだよ、とか・・・wワッカンネー

今回仕事でバックエンドを担当していて、フロントから「2019/01/18」という形式で日付を渡されました。
これを単純にobjectにしてみます。

const date = '2019/01/18';
const result = new Date(date);
console.log(result); // 2019-01-17T15:00:00.000Z

一見日時がズレてるように見えますが9時間前、つまりUTCのISO形式のobjectになっているので、mongoDBにそのまま渡せば日本時間で検索できます。

JavaScriptはブラウザの種類やサーバーなどの環境で、返される日時がUTCだったりします。

その場合resultが2019-01-18T00:00:00.000Zになると思うのですが、そのまま使うと日本時間ではなくなります。

強制的にUTCにされたらいったんUNIXtimeに変換し、momentモジュールを使用して、UTCに変換してからobjectに変換するといいと思います。

const moment = require('moment');

const date = '2019/01/18';
const unixTime = Date.parse(date); // 1547737200000
const momentTime = moment(unixTime); // moment("2019-01-18T00:00:00.000")
const result = new Date(momentTime); // 2019-01-17T15:00:00.000Z
console.log(result);

面倒くさいっすな〜・・・。

momentのオフィシャル・ドキュメントはこちら。
https://momentjs.com/docs/

objectに変換した日時を使ってmongoDBで2019/01/18以降のデータを検索する場合は、以下のようなコードになります。

'use strict';

const MongoClient = require('mongodb').MongoClient;

const main = async () => {
  const client = await MongoClient.connect('mongodb://localhost:27017', { useNewUrlParser: true });
  const col = client.db('hoge').collection('foo');

  // 日時をobjectに変換
  const date = '2019/01/18';
  const dateObj = new Date(date); // 2019-01-17T15:00:00.000Z

  const query = { 'date': { '$gte': dateObj } };
  const result = await col.find(query).toArray();
  console.log(result);
  client.close();
};

main();

node.jsはすぐに古い仕様を切り捨てるので、古い記事にはくれぐれもご注意を。

node.js: mongoDBで指定したフィールドのみ表示、ソート、リミットを設定する

node.jsは日々バージョンアップしていくので、巷に溢れたサンプルコードがすぐ役立たずになります。
node.jsがバージョンアップすると、モジュールもスゴいスピードでバージョンアップしていきます。

mongoDBで表示するフィールドを指定して、ソートしてfindしたかっただけなのですが、あまりにも古いソースが多かったので備忘録です。

mongodb driverの公式サイト最新版(v3)はこちら。
http://mongodb.github.io/node-mongodb-native/3.0/api/Collection.html

下記は表示させるフィールドを指定して、フィールド「user_id」でソート(降順)、10件のみ表示、ただしフィールド「_id」は非表示にするという例です。

'use strict';

const MongoClient = require('mongodb').MongoClient;

const main = async () => {
  const client = await MongoClient.connect('mongodb://localhost:27017', { useNewUrlParser: true });
  const db = client.db('hoge');
  const col = db.collection('foo');

  const project = {
    _id: 0,
    user_id: 1,
    mail_address: 1
  };
  const sort = [['user_id', -1]];
  const limit = 10;

  const data = await col.find().project(project).sort(sort).limit(limit).toArray();
  client.close();
};

main();

今までずっと古いサンプルコードに騙されていたのですが、mongodb driver Ver.3ではフィールドを表示、非表示する場合「fields」プロパティではなくて「project」メソッドです。

sortさせる場合昇順は1、降順は-1です。

オプションをfind()の中に入れるのではなくて、芋づる形式で書くんですね。

他にもいっぱい罠がありそうなので、当たり前ですが公式マニュアルを読みましょう! 見にくいのが仇ですがw

node.js:mongoDB接続の雛形(ES2017準拠)

node.jsでmongoDBに接続する際のひな型、現時点での最新版です。

このような↓collectionを予め作っておくとします。

> db.test.find();
{ "_id" : ObjectId("5b548f2fb19c25cc3796e11c"), "name" : "福沢諭吉", "price" : "10000" }
{ "_id" : ObjectId("5b548f2fb19c25cc3796e11d"), "name" : "夏目漱石", "price" : "1000" }
{ "_id" : ObjectId("5b548f2fb19c25cc3796e11e"), "name" : "野口英世", "price" : "1000" }
{ "_id" : ObjectId("5b548f2fb19c25cc3796e11f"), "name" : "新渡戸稲造", "price" : "5000" }
{ "_id" : ObjectId("5b548f2fb19c25cc3796e120"), "name" : "樋口一葉", "price" : "5000" }

単純にmongoの中身を表示させる場合、

'use strict';

const MongoClient = require('mongodb').MongoClient;

const main = async () => {
  const client = await MongoClient.connect('mongodb://localhost:27017', { useNewUrlParser: true });
  const db = client.db('mongo_test');
  const col = db.collection('test');

  const data = await col.find().toArray();
  console.log(data);

  client.close();
};

main();

結果は、

[ { _id: 5b548f2fb19c25cc3796e11c, name: '福沢諭吉', price: '10000' },
  { _id: 5b548f2fb19c25cc3796e11d, name: '夏目漱石', price: '1000' },
  { _id: 5b548f2fb19c25cc3796e11e, name: '野口英世', price: '1000' },
  { _id: 5b548f2fb19c25cc3796e11f, name: '新渡戸稲造', price: '5000' },
  { _id: 5b548f2fb19c25cc3796e120, name: '樋口一葉', price: '5000' } ]

今度は繰り返しの場合の書き方です。
「福沢諭吉」だったらmongoDBのドキュメントを「聖徳太子」に置換してみましょう。

'use strict';

const MongoClient = require('mongodb').MongoClient;

const main = async () => {
  const client = await MongoClient.connect('mongodb://localhost:27017', { useNewUrlParser: true });
  const db = client.db('mongo_test');
  const col = db.collection('test');

  const cursor = col.find();
  while (await cursor.hasNext()) {
    const data = await cursor.next();

    if (data.name == '福沢諭吉') {
      const sub = { name: "聖徳太子" };
      await col.updateOne({ _id: data._id }, { $set: sub });
    }
  }
  client.close(); // コールバックしないのでawaitいらない
};

main();

mongoDBで確認すると、諭吉だった部分だけ

{ "_id" : ObjectId("5b548f2fb19c25cc3796e11c"), "name" : "聖徳太子", "price" : 10000 }

に変更されています。

async/awaitの使い方が本当に難しいのですが、ドキュメントの数を30万くらいに増やしたり、awaitをわざと外してみて挙動を確認すると、なんとなく段々とわかってきます、たぶん。。。

mongoDB: 起動しない時の対処法

2017-11-23T18:22:04.091+0900 W NETWORK  [thread1] Failed to connect to 127.0.0.1:27017, in(checking socket for error after poll), reason: Connection refused
2017-11-23T18:22:04.091+0900 E QUERY    [thread1] Error: couldn't connect to server 127.0.0.1:27017, connection attempt failed :
connect@src/mongo/shell/mongo.js:237:13
@(connect):1:6
exception: connect failed

このエラーでmongoDBが起動できないことが頻繁にあるのですが、その時の対処法です。

$ sudo mongod --dbpath /var/lib/mongodb --logpath /var/log/mongodb.log

ログのパスを指定しないとなぜか起動しないが、時間がないので今は原因は追わないでおきます。

今度は/data/dbがないよというエラーが出ました。
これもポピュラーなようですね。

mongod -dbpath /usr/local/var/mongodb

これで解決。
ん〜、色々いじりすぎて、そろそろこのMacヤバい。。。

PHP:Mac + mongoDBインストール

PHPでmongoDBを使うにはmongo本体だけではなく、ドライバのインストールも必要になります。
状況は刻一刻と変わるようですので、現時点での最新情報となります。

ググって見つかる日本語の情報は古くて使えませんでした。
あまりメジャーではないようなので、ドキュメントは英語で探さないと最新版は見つからないと思います。

最初XAMPPに入れようとしたのですが、XAMPP側にバグがまだ残っているようで入りませんでした。
インストールはできますが、Apacheが起動しなくなります。
https://github.com/mongodb/mongo-php-driver/issues/247

ですので元々入っているPHPを5.6から7.1にバージョンアップしました。

【今回インストールした環境】
MacOS Sierra 10.12.6
PHP Version 7.1.11
MongoDB 3.4.10

PHP5時代の古い情報に気をつけてください。
古いドライバを入れても動作しません。

mongoDBを先にインストールするのではなくて、PHPを7にまずはバージョンアップしてください。
PHPのインストールはこちらが詳しいです。
「osxにphp7.1をインストール」
http://yugoyamamoto.hateblo.jp/entry/2017/02/17/115532

httpd.confに下記設定が入っていなければ一番下にでも追記するのを忘れずに。

LoadModule php7_module /usr/local/opt/php71/libexec/apache2/libphp7.so

<FilesMatch .php$>
  SetHandler application/x-httpd-php
</FilesMatch>

httpd.confの場所は、

/etc/apache2/httpd.conf

次にmongoDB本体を入れます。
「HomebrewでMacにMongoDBをインストールした時のメモ」
https://qiita.com/Frog_woman/items/f8a70286c7f1c4d5fc02

で、ここからがこの記事のメインです。
PHPでmongoDBを扱うにはドライバが必要になります。
PHPオフィシャルサイトに載っている情報が古くて、自分はここで2日間くらい潰しました。

日本語のドキュメントはもうダメダメなので英語で探したところ、「オフィシャルに載っている入れ方はもう古いのでhomebrewで入れるように」との記事を見つけました。

先に言ってくれよぉぉぉっ!!!

というわけでPECLでインストールしてはダメです。
homebrewでインストールします。

まだ日本語化されていないPHPオフィシャル
http://php.net/manual/ja/mongodb.installation.homebrew.php

$ brew install php71-mongodb

http://localhostを叩くと「It works!」という文字が現れると思うのですが、このファイルの置き場所はここ。

/Library/WebServer/Documents/

ただここをrootにしてしまうと、フォルダやファイルを作ったり削除するたびにパスワードを聞かれ非常にウザいです。
そこでrootを、

/Users/ユーザー名/Sites

に変更します。
ディレクトリ「Sites」は作っておいてください。

CotEditorが使えないのでviを使います。

/private/etc/apache2/users/httpd.conf

変更前:

DocumentRoot "/Library/WebServer/Documents"
<Directory "/Library/WebServer/Documents">

変更後:

DocumentRoot "/Users/ユーザー名/Sites"
<Directory "/Users/ユーザー名/Sites">

 
/private/etc/apache2/users/ユーザー名.conf
※usersディレクトリがない場合は新たに作る。

<Directory "/Users/ユーザー名/Sites">
  AllowOverride All
  Options Indexes FollowSymLinks Multiviews
  Require all granted
</Directory>

上記をマルっとコピーして使って大丈夫です。
その後ターミナルでApacheを再起動します。

$ sudo apachectl restart

phpinfoでmongoDBがあれば成功です。

<?
phpinfo();
?>

 

mongoDBの動作テストもしちゃいましょう。
PHPファイルに下をコピペして、

<?php
$manager = new MongoDB\Driver\Manager("mongodb://localhost:27017");
var_dump($manager);
?>

ブラウザで見ます。
下はソースですが、

object(MongoDB\Driver\Manager)#1 (2) {
  ["uri"]=>
  string(25) "mongodb://localhost:27017"
  ["cluster"]=>
  array(0) {
  }
}

こんな感じでobjectが落ちてくればPHPでmongoが動作しています。