コンパイラかく語りき

import { Fun } from 'programming'

ExpressとRethinkDBで作るRESTfulなWebAPI

ExpressとRethinkDBで作るRESTfulなWebAPIです。 RethinkDB公式のこちらを参考にしました。

ひとまず、Expressでルーティングを定義して、RethinkDB上のデータへのCRUD処理ができるところまで。

前提条件

  • RethinkDBをインストールしておく。
  • Express起動前にローカルでRethinkDBを立ち上げておく。

パッケージ

"async": "^2.0.1",
"body-parser": "^1.15.2",
"express": "^4.14.0",
"rethinkdb": "^2.3.3"

config

// ./config.js
module.exports = {
  rethinkdb: {
    host: 'localhost',
    port: 28015,
    authKey: '',
    db: 'rethinkdb_ex'
  },
  express: {
    port: 3000
  }
}

本体

雑なベタ貼りですみません…。都度コメント書いてあります。 ポイントとして、ReQLの実行結果はプロミスが返ります。

// ./server.js
const r = require('rethinkdb')
const express = require('express')
const async = require('async')
const bodyParser = require('body-parser')

const config = require(__dirname + '/config')

const app = express()

// index.htmlとその他のフロントエンドアセットをサーブする。
app.use(express.static(__dirname + '/public'))

app.use(bodyParser.json())

/*
*  すべてのTodoアイテムを取得
*/
const listTodoItems = (req, res, next) => {
  r.table('todos').orderBy({index: 'createdAt'}).run(req.app._rdbConn, (err, cursor) => {
    if(err) {
      return next(err)
    }
    
    cursor.toArray((err, result) => {
      if(err) {
        return next(err)
      }
      
      res.json(result)
    })
  })
}

/*
*  新しくTodoを作成してinsert
*/
const createTodoItem = (req, res, next) => {
  const todoItem = req.body
  todoItem.createAt = r.now()
  
  console.dir(todoItem)
  
  r.table('todos').insert(todoItem, {returnChanges: true}).run(req.app._rdbConn, (err, result) => {
    if(err) {
      return next(err)
    }
    
    res.json(result.changes[0].new_val)
  })
}

/*
*  特定のTodoを取得
*/
const getTodoItem = (req, res, next) => {
  const todoItemID = req.params.id
  
  r.table('todos').get(todoItemID).run(req.app._rdbConn, (err, result) => {
    if(err) {
      return next(err)
    }
    
    res.json(result)
  })
}

/*
* Todoの更新
*/
const updateTodoItem = (req, res, next) => {
  const todoItem = req.body
  const todoItemID = req.params.id
  
  r.table('todos').get(todoItemID).update(todoItem, {returnChanges: true}).run(req.app._rdbConn, (err, result) => {
    if(err) {
      return next(err)
    }
    
    res.json(result.changes[0].new_val)
  })
}

/*
* Todoの削除
*/
const deleteTodoItem = (req, res, next) => {
  const todoItemID = req.params.id
  
  r.table('todos').get(todoItemID).delete().run(req.app._rdbConn, (err, result) => {
    if(err) {
      return next(err)
    }
    
    res.json({success: true})
  })
}

/*
* page-not-found ミドルウェア
*/
const handle404 = (req, res, next) => {
  res.status(404).end('not found')
}

/*
* 500ページを送り返し、エラーをロギングする
*/
const handleError = (err, req, res, next) => {
  console.error(err.stack)
  res.status(500).json({err: err.message})
}

/*
* DB接続をストアして、ポートのリッスンを始める
*/
const startExpress = (connection) => {
  app._rdbConn = connection
  app.listen(config.express.port)
  console.log(`Listening on port ${config.express.port}`)
}

// ルーティング定義
app.route('/todos')
  .get(listTodoItems)
  .post(createTodoItem)

app.route('/todos/:id')
  .get(getTodoItem)
  .put(updateTodoItem)
  .delete(deleteTodoItem)

app.use(handle404)

app.use(handleError)

/*
* RethinkDBに接続して、必要なテーブルを作成してインデックスを貼り、expressを起動する
*/
async.waterfall([
  // RethinkDBに接続
  function connect(callback) {
    r.connect(config.rethinkdb, callback)
  },
  // DBを無ければ作成
  function createDatabase(connection, callback) {
    r.dbList().contains(config.rethinkdb.db).do((containsDb) => {
      return r.branch(
        containsDb,
        {created: 0},
        r.dbCreate(config.rethinkdb.db)
      )
    }).run(connection, (err) => {
      callback(err, connection)
    })
  },
  // テーブルを無ければ作成
  function createTable(connection, callback) {
    r.tableList().contains('todos').do((containsTable) => {
      return r.branch(
        containsTable,
        {created: 0},
        r.tableCreate('todos')
      )
    }).run(connection, (err) => {
      callback(err, connection)
    })
  },
  // インデックスが無ければ作成
  function createIndex(connection, callback) {
    r.table('todos').indexList().contains('createdAt').do((hasIndex) => {
      return r.branch(
        hasIndex,
        {created: 0},
        r.table('todos').indexCreate('createdAt')
      )
    }).run(connection, (err) => {
      callback(err, connection)
    })
  },
  // インデックスをwaitさせる
  function waitForIndex(connection, callback) {
    r.table('todos').indexWait('createdAt').run(connection, (err, result) => {
      callback(err, connection)
    })
  }
], (err, connection) => {
  if(err) {
    console.error(err)
    process.exit(1)
    return
  }
  
  // express起動
  startExpress(connection)
})

このスクリプトを、$ node server.jsで起動します。

様子

左がAdvanced REST clientというクロームAppで、HTTPリクエストを送信しています。右がRethinkDBのAdmin画面で、データの変更を検知できます。

express-rethinkdb-rest-api.gif