2016/11/28

Node.js + Express4 + Sequelize + PostgreSQLでRESTful APIサーバをつくる

3tierシステム(Webサーバ+APサーバ+DBをサーバ)のWebサービスをつくる一人プロジェクトをしており、今回はサーバサイドのNode.js + Express4 + Sequelize + PostgreSQLの構成でRESTfulなAPIサーバをつくった。

実行環境は以下のとおり。

  • Windows7
  • Node.js >= 6.0
  • Express - 4.14.0
    • Node.js用のWebフレームワーク
  • body-parser - 1.15.2
    • POSTでボディパラメータを受け取るため
  • Sequelize - 3.27.0
    • PromiseベースのORMライブラリ
  • pg - 6.1.0
    • PostgreSQLのNode.js用クライアント
  • PostgreSQL - 9.4.7
    • さくらのクラウド データベースアプライアンス(プレビュー版)



DBサーバ(PostgreSQL)を用意する


今回はイチからDB環境を用意するのが面倒だったので、さくらのクラウドのデータベースアプライアンスを使った。現在はプレビュー版で無料で利用できるので、ありがたく使わせていただいた。
ローカルやサーバのセットアップが不要なので、さっと使いたいときに便利!


RESTful APIサーバをつくる


DBサーバとは別に、RESTful APIサーバ(APサーバ)をつくる。
ディレクトリ構成は以下のとおり。
.
├─ app.js
├─ config.json
├─ package.json
├─models
│    ├─ index.js
│    └─ user.js
└─routes
      └─ user.js

app.jsがメイン。
models配下が、データベース関連。index.jsにSequelizeの定義、user.jsにテーブルの定義が書かれている。
routes配下が、RESTful APIのエンドポイントごとにまとめられている。


設定ファイル


// config.json
{
    "hostname": "IPアドレス/ホスト名",
    "database": "develop(データベース名)",
    "username": "develop(ユーザ名)",
    "password": "********(パスワード)"
}
今回はさくらのクラウドのデータベースアプライアンスを使っているので、hostnameはアプライアンスのIPアドレス、databaseとusernameはデフォルトユーザ名、passwordは設定したパスワードを指定する。


package.json


// package.json
{
  "name": "node-postgres-sample",
  "dependencies": {
    "body-parser": "^1.15.2",
    "express": "^4.14.0",
    "pg": "^6.1.0",
    "sequelize": "^3.27.0"
  }
}


Modelsの定義

データベースのModelsを定義する。まずはメインとなるmodels/index.js
内容的には、設定が書かれたconfig.jsonを読み込んでコネクションの定義を行い、次にmodels配下のjsファイルを読み込む。
// ./models/index.js
const Sequelize = require('sequelize');
const config = require('../config.json');
const fs = require('fs');
const path = require('path');

const sequelize = new Sequelize(
    config.database,
    config.username,
    config.password,
    {
        host: config.hostname,
        dialect: 'postgres'
    }
);
const db = {};

// models配下のjsファイルを読み込む
fs.readdirSync(__dirname)
    .filter(file => file.indexOf('.js') && file !== 'index.js')
    .forEach(file => {
        const model = sequelize.import(path.join(__dirname, file));
        db[model.name] = model;
    });

// 今回はassociateの定義をしないので実行されない
Object.keys(db).forEach(modelName => {
    if ('associate' in db[modelName]) {
        db[modelName].associate(db);
    }
});

db.sequelize = sequelize;
db.Sequelize = Sequelize;

module.exports = db;

次にModelsの定義をする。
Userというテーブル名で、フィールドはusernameとemailだけという簡単な定義。
// ./models/user.js
module.exports = (sequelize, DataTypes) => {
    const User = sequelize.define('User', {
        username: DataTypes.STRING,
        email: DataTypes.STRING
    });

    return User;
};


RESTful APIのエンドポイントの定義


// ./routes/user.js
const models = require('../models');
const express = require('express');
const router = express.Router();

router.get('/', (req, res) => {
    models.User.findAll().then(result => {
        if (result && result.length > 0) {
            res.json(result);
        } else {
            res.status(204);
            res.send();
        }
    }).catch(err => {
        res.status(409);
        res.json(err);
    });
});

router.get('/:id', (req, res) => {
    models.User.findOne({
        where: {
            id: req.params.id
        }
    }).then(result => {
        if (result) {
            res.json(result);
        } else {
            res.status(204);
            res.send();
        }
    }).catch(err => {
        res.status(409);
        res.json(err);
    });
});

router.post('/', (req, res) => {
    models.User.create({
        username: req.body.username,
        email: req.body.email
    }).then(result => {
        res.status(201);
        res.send(result);
    }).catch(err => {
        res.status(409);
        res.send(err);
    });
});

router.put('/:id', (req, res) => {
    models.User.update({
        username: req.body.username,
        email: req.body.email
    }, {
        where: {
            id: req.params.id
        }
    }).then(result => {
        res.status(204);
        res.json(result);
    }).catch(err => {
        res.status(409);
        res.json(err);
    });
});

router.delete('/:id', (req, res) => {
    models.User.destroy({
        where: {
            id: req.params.id
        }
    }).then(result=> {
        res.status(204);
        res.json(result);
    }).catch(err => {
        res.status(409);
        res.json(err);
    });
});

module.exports = router;


エントリーポイント


// ./app.js
const http = require('http');
const express = require('express');
const bodyParser = require('body-parser');
const port = process.env.PORT || 3000;

const models = require('./models');
const user = require('./routes/user');

const app = express();
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

const router = express.Router();
router.use('/user', user);

app.use('/api', router);

models.sequelize.sync().then(() => {
    http.createServer(app).listen(port);
    console.log('listening');
});


ちなみに、APIのテストはPostmanというChromeアプリを使うと便利。
ただChromeアプリが2018年までに段階的に終了になると発表されたので、おそらく2017年上半期にはPostmanもインストールできなくなり、2018年には使えなくなるかもしれないので注意。


Sequelizeの詳しい使い方は、以下の記事を参考にしてほしい。
本番で運用する場合は、これらに加え、pm2というプロセス管理のモジュールを使ったり、log4jsとかでロギングしたり、エラー処理をもうちょっとなんとかしたり、と改善点はいくつもあるが、今回はここまで。



参考サイト




以上

written by @bc_rikko

0 件のコメント :

コメントを投稿