Node.js+Express+EJSを使い遊んだところで、次はMongoDBとなるのは人情(笑)タイトルにあるように、WordPress REST APIで得たJSONをMongoDBへ保存したい。これができれば、Expressで最小限の軽いAPIを作りクライアントとやり取りしたり、Node.jsで書いたViewに組み込みEJSへ渡すことも簡単!
MongoDBはNoSQLと呼ばれるDBの一種。JSONフォーマットのまま、keyとvalueを操作でき、SQLでdb、table、row、columnに該当する部分が順に、db、collection、document、fieldとなるイメージだ。
今回、WordPress REST APIで得たJSONをMongoDBで扱うのに、db=wp、collection=tags、categories、postsとした。Node.jsとMongoDBのインストールは省略。既に作動するものとして話を進めたい。macOSでもWindowsのBashでも簡単に環境を構築できる。
使用するモジュールは以下の通り。事前にnpm install xxxx −−saveで用意する。
早速コードその1。ここはロジックに必要な変数などと、MongoDBの初期化まで(}はまだ閉じてないので要注意。その1と2をコピペすれば作動するが、タイムアウトなどのエラー処理は含まれていない)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
var mongoClient = require('mongodb').MongoClient; var mongodb_url = 'mongodb://localhost:27017/wp'; var assert = require('assert'); var http = require('http'); var base_url = 'http://blog.iwh12.jp'; var per_page = 10; var POSTS = 'posts'; var CATEGORIES = 'categories'; var TAGS = 'tags'; setCollection(TAGS, '/wp-json/wp/v2/tags', false); setCollection(CATEGORIES, '/wp-json/wp/v2/categories', false); setCollection(POSTS, '/wp-json/wp/v2/posts', true); // get data from API and set collection function setCollection(collection, url, exit) { var api_url = base_url + url + '?per_page=' + per_page; var max = 0; var total = 0; var count = 0; var db; // connect to mongo server mongoClient.connect(mongodb_url, function(err, mongodb) { assert.equal(null, err); console.log("Connected to server: wp." + collection); db = mongodb; db.collection(collection).drop(function(err) { console.log('Drop collection: ' + collection); }); }); |
ここでの肝は、はじめにcollection=tags、categories、postsをdropしている点となる(有無を確認せずいきなりdropでも動く)。これを行わないとプログラムを実行するたびに(ロジックにもよるが)各collectionに同じものも含め追加されてしまうからだ。
もともとは全件読み込んだ後にこのプログラムを動かすと追加分だけマージする予定だったが、考えてみればAPIから簡単に得られるのは全件数のみ。
DBの件数と比較してもし+αになっていても、純粋に+α分追加したのか、1件削除+α+1件でその値になったのか知る由もない。また古い内容も何かのタイミングで再編集している可能性がある。
そう考えると、どのみちAPIを使うとデータが飛んできてしまうので、小細工は不要。全件リロードするため、事前にcollectionをdropしている(もちろん全件削除でもいい)。また、dbとcollectionは、無いと自動的に作られる。
コードその2は、各collectionのJSONを得て、MongoDBへ保存している部分。全て再帰呼出しなので件数に制限は無い。exitは、全件取得後、プログラムを終了するか、継続するかのフラグとなる。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
// get from API getFromAPI(0); // get JSON with paginate function getFromAPI(n) { http.get(api_url + '&page=' + (n + 1), (json) => { var body = ''; json.setEncoding('utf8'); json.on('data', (chunk) => { body += chunk; }); json.on('end', () => { if (max == 0) { max = json.rawHeaders[parseInt(json.rawHeaders.indexOf('X-WP-TotalPages')) + 1]; total = json.rawHeaders[parseInt(json.rawHeaders.indexOf('X-WP-Total')) + 1]; } var d = JSON.parse(body); d.forEach(function(elem, index) { db.collection(collection).insertOne(elem).then(function(err, r) { console.log(collection, 'page: ' + (n + 1),'item: ' + (index + 1)); count++; if (exit && total == count) { db.close(); process.exit(0); } }); }) if (max == n + 1) return; getFromAPI(n + 1); }); }); } } |
ここは前回の「WordPress 4.7のREST APIを使った記事閲覧プログラム、Post数無制限版」と内容がほぼ同じ。forEachでdocument毎にinsertOne()している。ただMongoDBへの書き込みも非同期でちょっと工夫したのがexitの部分だ。
と言うのも、何も考えずにコーディングすると、if (max == n + 1) return;のところへ、exit==trueならprocess.exit(0);と書きそうになる。が、このタイミングでは、まだMongoDBへの書き込みが終わっておらず実際の件数と合わなくなるケースが発生する。
確実なのはCallback内で、total(=X-WP-Total)と書き込み回数(=count)が同じになったタイミングだ。
実はnの要素を持つJSONをまとめてInsertできるinsertMany()というmethodもあり、forEach無しでper_page分、いきなり全部保存も可能。こちらの方がコードもスッキリするし、おそらくMongoDBの作動も速い。
1 2 3 4 5 6 7 8 9 |
// forEach、total、countが不要 var d = JSON.parse(body); db.collection(collection).insertMany(d).then(function(err, r) { console.log(collection, 'page: ' + (n + 1)); if (exit && n + 1 == max) { db.close(); process.exit(0); } }); |
これで無事JSON全件が保存された。確認はこんな感じでできる。
$ mongo
> use wp
switched to db wp
> show collections
categories
posts
tags
> db.posts.count();
39
> db.posts.find({id:769}, {title:1,_id:0});
{ “title” : { “rendered” : “WordPress 4.7のREST APIを使った記事閲覧プログラム、Post数無制限版” } }
※1 :1は表示、:0は非表示(_id=primary keyは必ず表示される)
※2 同様にcollectionのtags、categoriesも操作できる。dbの削除はdb.dropDatabase();
日頃MySQLにお世話になってる身としては、何といっても感動は、詳細を知らないJSONをinsertOne()やinsertMany()でドン!と入れれることと、JSONのまま検索などができること。この手のシステムに向いているDBと言える。
後は、冒頭に書いたようにExpressで簡単なAPIのルーティングを書き、クライアントとやり取りするか、Express+EJSでViewを書くなどいろいろパターンが考えられる。ただ、連日遊び過ぎでちょっと燃え尽きたので続きが何時になるかは未定(笑)