Express, Node.js, RethinkDB, Thinkyを使って作るリアルタイムWebAPI

連日似たような投稿ばかりですみません…。

以前、こんな投稿をしました。

- [ExpressとRethinkDBで作るRESTfulなWebAPI](http://qiita.com/chuck0523/items/c7fc23952868927560f5)
- [Thinkyを使ったRethinkDB REST API](http://qiita.com/chuck0523/items/137a54925ae1c3845a41)

この記事は↑の２つを混ぜつつ、実際に動くレベルまで持っていた記録になります。

つまり、RethinkDBへの操作をORMのThinkyでカンタンに行いつつ、Expressを使ってRESTfulなWebAPI化しました。

ソースコードだけみたい、という方は[こちら](https://github.com/chuck0523/learning-langs/tree/gh-pages/js/rethinkdb/express-thinky-api)にアップしてあります。


# 前提条件

RethinkDBをインストールして、ローカルで立ち上げておいてください。
MacOSの方はhomebrewからどうぞ。

```bash
$ brew update && brew install rethinkdb
$ rethinkdb # 立ち上げ
```


# セット・アップ

必要なパッケージをインストールします。

```bash
$ npm init -y
$ npm install --save express thinky body-parser
```

package.jsonはだいたいこんな感じ。

```json
{
  "name": "express-thinky-api",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "body-parser": "^1.15.2",
    "express": "^4.14.0",
    "thinky": "^2.3.6"
  }
}
```

# リアルタイムWebAPI作成

ここからは、コードを書いてリアルタイムWebAPIを作っていきます。

## Thinky初期化

まずは、thinkyを初期化します。その際に、オブジェクトでセット・アップ情報を渡します。

```js
// ./thinky.js
var thinky = require('thinky')({
  host: 'localhost',
  port: 28015,
  db: 'People'
})

module.exports = thinky
```

## スキーマ定義

まずは、操作対象となるモデルを作成します。
Node.jsからRethinkDBを操作するthinkyをrequireします。

```js
// ./schema.js
const thinky = require('./thinky')
var r = thinky.r

var People = thinky.createModel('People', {
  firstName: String,
  lastName: String,
  coolnessFactor: Number,
  date: { _type: Date, default: r.now() }
})
People.ensureIndex('date')

module.exports = People
```

Peopleという名前でモデルを生成しました。

Thinkyの使い方については、以下の記事をご覧ください。

[node.jsでRethinkDBを扱うためのORM "Thinky" を試してみた](http://qiita.com/chuck0523/items/c0bd2b3b92b6884205dc)


## リアルタイム化

RethinkDBの特徴はリアルタイム性です。ここでは、Peopleモデルに対して、リアルタイムロギング機能を付け加えます。

```js
// ./realtime.js
const stringify = (doc) => {
  return JSON.stringify(doc, null, 2)
}

const realtimeLogging = (Model) => {
  Model.changes().then((feed) => {
    feed.each((err, doc) => {
      if (err) {
        console.log(err)
        process.exit(1)
      }
      
      if (doc.isSaved() === false) {
        console.log("The following document was deleted:")
        console.log(stringify(doc))
      } else if (doc.getOldValue() === null) {
        console.log("A new document was inserted:")
        console.log(stringify(doc))
      } else {
        console.log("A document was updated.")
        console.log(stringify(doc.getOldValue()))
        console.log("New value:")
        console.log(stringify(doc))
      }
    })
  }).error((err) => {
    console.log(err)
    process.exit(1)
  })
}

module.exports = realtimeLogging
```

ちょっと長くなりましたが、ここでやっていることは以下のとおりです。

- リアルタイム ロギング関数の定義
  - changesメソッドを使って、変更を監視（データの作成・削除・更新がフックになります）
  - 変更のあったドキュメント（いわゆるRow）をログ出力


## API作成

APIを作成します。ここでは、ルーティングに応じて呼ばれる関数を定義します。

```js
'use strict'

const thinky = require('./thinky')
const r = thinky.r

const People = require('./schema')

// 一覧取得
exports.list = function (req, res) {
  People.orderBy({ index: r.desc('date') }).run().then((people) => {
    res.json(people)
  }).error((err) => {
    res.json({ message: err })
  })
}

// 作成
exports.add = function (req, res) {
  var person = new People(req.body)
  person.save().then((result) => {
    res.json(result)
  }).error((err) => {
    res.json({ message: err })
  })
}

// 取得
exports.get = function (req, res) {
  People.get(req.params.id).run().then((person) => {
    res.json(person)
  }).error((err) => {
    res.json({ message: err })
  })
}

// 削除
exports.delete = function (req, res) {
  People.get(req.params.id).run().then((person) => {
    person.delete().then((result) => {
      res.json(result)
    }).error((err) => {
      res.json({ message: err })
    })
  }).error((err) => {
    res.json({ message: err })
  })
}

// 更新
exports.update = function (req, res) {
  People.get(req.params.id).run().then((person) => {
    if(req.body.firstName) {
      person.firstName = req.body.firstName
    }
    
    if(req.body.lastName) {
      person.lastName = req.body.lastName
    }
    
    if(req.body.coolnessFactor) {
      person.coolnessFactor = parseInt(req.body.coolnessFactor)
    }
    person.data = r.now()
    
    person.save().then((result) => {
      res.json(result)
    }).error((err) => {
      res.json({ message: err})
    })
  })
}
```

詳しくはReQL（RethinkDBのクエリ）のAPIをご覧ください。

https://www.rethinkdb.com/api/javascript/

## エントリーポイント作成

エントリーポイントとなるファイルを作成します。コンソールから呼ばれて実行されるファイルですね。

```js
// ./server.js
const express = require('express')
const bodyParser = require('body-parser')

const app = express()

app.use(express.static(__dirname + '/public'))
app.use(bodyParser.json())

// リアルタイムロギング付与
const People = require('./schema')
const realtimeLogging = require('./realtime')
realtimeLogging(People)

// API利用
const api = require('./api')
app.get('/api/people', api.list)
app.get('/api/people/:id', api.get)
app.delete('/api/people/:id', api.delete)
app.put('/api/people/:id', api.update)
app.post('/api/people', api.add)

app.listen(3000)
```


# 起動

スクリプトを叩いて、WebAPIを立ち上げましょう。

```bash
$ npm start
```

あるいは、

```bash
$ node server.js
```
　
これで、http://localhost:3000/api/people なんて叩くと一覧が取得できます。
データ操作を行うたびに、コンソールにログが出力されます。

# 余談

GUIツールが欲しい方は、ReQLProがオススメです。

- [RethinkDBのReQLProというGUIツールがいい感じ
](http://qiita.com/chuck0523/items/57c36dc5d4f22d9fb6c4)