Node.js + Express + TypeScript でREST APIを作成してみよう

Node.js, Express, Typescript でREST APIを作成する手順をまとめます。自分が好きな情報を詰め込んでいるので、必要ない物は省いてカスタマイズしてください。

# 準備
Node.jsとnpmがインストール済みであることを `node -v`, `npm -v` のコマンドを実行して確認します。
開発を行うディレクトリで `npm init` を実行して package.json が作成されることを確認します。いくつか質問が出てきますが全てそのままエンターを押して大丈夫です。

##  必要なライブラリなどのインストール
express(4系)やTypescript用のパッケージをインストールします。

```sh:
$ npm install express --save
$ npm install helmet --save
$ npm install cors --save
$ npm install mongodb mongoose --save
$ npm install body-parser --save
$ npm install moment --save
$ npm install -D typescript @types/node 
$ npm install -D @types/express @types/helmet @types/cors @types/mongoose
$ npm install -D ts-node
```

npmを使ってインストールしていきます。
```--save```のオプションをつけるとpackage.jsonの `dependencies` に書き込まれます。
```-D```のオプションをつけるとpackage.jsonの `devDependencies` (開発用) に書き込まれます。


## ディレクトリ構成
コードの見通しを意識して、とりあえず次のような構成にします。

>express-api
　|---- /app
　|　　　└ app.ts
　|　　　└ /models 
　|ㅤ　　└ /routes
　|　　　└ /services
　|　　　
　|---- package.json

**app.ts**  これを実行してサーバーを起動します
**/models** データベースのモデル情報
**/routes**  各apiの振り分け
**/services** サービスの処理
**package.json** パッケージ管理

## Typescript準備
`npx tsc --init` でtsconfig.jsonファイルを作成します。
tsconfig.jsonのtargetがes5だった場合、es6に変更します。

# コードを書く

## Hello World

まずは`app.ts`に以下を記述して、hello world が返却されることまで確認しましよう。

```ts:app.ts
// ライブラリ読み込み
import express from 'express';
import helmet from 'helmet';
import cors from 'cors';
const app = express();
app.use(helmet());
app.use(cors());
const bodyParser = require('body-parser');

//body-parserの設定
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

const port = process.env.PORT || 3000; // port番号を指定

app.get('/helloWorld', (req, res) => {
    res.status(200).send({ message: 'hello, world' });
});

//サーバ起動
app.listen(port);
console.log('listen on port ' + port);
```

`npx tsc` コマンドを実行するとコンパイルされ、jsファイルに変換されます。
`node app/app.js` コマンドでファイルを実行します。
コンソールに `listen on port 3000` が表示されたことを確認したら、postmanを立ち上げて、http://localhost:3000/helloWorld にGETリクエストを送りましょう。

以下のようにレスポンスが返ってくれば成功です！！

```json:
{
  "message": "Hello, world"
}
```

ts-node コマンドを利用すると、  tsc -> node の実行を自動的に行ってくれるので開発効率をあげることができます。`npx ts-node app/app.ts` で実行してみましょう。

ソースコードの変更を検知するたびに自動的に再実行してくれる ts-node-dev というコマンドもありますが、今回は省略します。

いずれも `Ctrl + C`で終了できます。

## apiをディレクトリで分けて綺麗にする
まず、```app.ts```を以下のように書き換えます。

```ts:app.ts
// ライブラリ読み込み
import express from 'express';
import helmet from 'helmet';
import cors from 'cors';
const app = express();
app.use(helmet());
app.use(cors());
const bodyParser = require('body-parser');

//body-parserの設定
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

const port = process.env.PORT || 3000; // port番号を指定

// ------ ルーティング ------ //
const router = require('./routes/');
app.use('/', router);

//サーバ起動
app.listen(port);
console.log('listen on port ' + port);
```

これでroutesでルーティングする設定になりました。続いて、routes配下に```index.ts```を作成して、以下のように記述しましょう。

```ts:index.ts
import express from 'express';
import helmet from 'helmet';
import cors from 'cors';
const app = express();
app.use(helmet());
app.use(cors());
// ルーティングする
const router = express.Router();

// routerにルーティングの動作を記述する
router.get('/helloWorld', (req, res) => {
    res.status(200).send({ message: 'Hello, world' });
});

// -------------------------------------------------
//  以下、何のルーティングにもマッチしないorエラー
// -------------------------------------------------

// いずれのルーティングにもマッチしない(==NOT FOUND)
app.use((req, res) => {
    res.status(404);
    res.render('error', {
      param: {
        status: 404,
        message: 'not found'
      },
    });
  });

//routerをモジュールとして扱う準備
module.exports = router;
```

ts-node コマンドを実行して、postmanでAPIを叩くと同じ結果が返ってくることを確認します。

続いて、サービスに設定していきましょう。
routes/index.tsにtestという名前のAPIを追加しましょう。

```ts:index.ts
import express from 'express';
import helmet from 'helmet';
import cors from 'cors';
import { TestService } from '../services/TestService';
const app = express();
app.use(helmet());
app.use(cors());
// ルーティングする
const router = express.Router();

// routerにルーティングの動作を記述する
router.get('/helloWorld', (req, res) => {
    res.status(200).send({ message: 'Hello, world' });
});

router.get('/test', (req, res, next) => {
    const service = new TestService();
    service
      .test()
      .then(result => res.status(200).send(result))
      .catch(next);
});

// -------------------------------------------------
//  以下、何のルーティングにもマッチしないorエラー
// -------------------------------------------------

// いずれのルーティングにもマッチしない(==NOT FOUND)
app.use((req, res) => {
    res.status(404);
    res.render('error', {
      param: {
        status: 404,
        message: 'not found'
      },
    });
});

//routerをモジュールとして扱う準備
module.exports = router;
```

そして、services配下に`TestService.ts`を作成して、以下のように記述します。

```ts:TestService.ts
//インターフェース
export interface ITest {
  test: string;
}

//クラス
export class TestService {
    public async test(): Promise<ITest> {
        return {
           test: 'test1'
       } 
    }
}
```

http://localhost:3000/test にGETリクエストを送り、以下のレスポンスが返ってくると成功です！

```json:
{
    "test": "test1"
}
```

続いてDB、Schemaを設定していきましょう。

## mongoDB準備

### mongoDBインストール

以下のコマンドでmongoDBをインストールします。
`brew tap mongodb/brew`
`brew install mongodb-community`

mongodbの設定ファイルが/usr/local/etc/mongod.confに作成されます。
中身を確認するとログとデータベースの保存先のパスが記述されています。

mongoDBの起動
`brew services start mongodb-community`

mongo コマンドでDBに接続できます。

### データ追加
以下コマンドで app1db というデータベースを作成します。
`use app1db`
データベースにswitchされるので、以下コマンドでデータを追加します。

```sh:
> db.test_user.insert({"user":"user1","email":"user1@gmail.com"})
> db.test_user.insert({"user":"user2","email":"user2@gmail.com"})
```

このコマンドで test_user というcollection (RDBMSでいうtableのようなもの) にBSONのフォーマットでuserなどの情報が保存される。

### DB接続部分の追加
mongoDBに接続する部分をapp.tsに追加します。

```ts
// mongodb
const mongoose = require('mongoose');
mongoose.Promise = global.Promise;
const options = {
    useUnifiedTopology : true,
	useNewUrlParser : true
}
mongoose.connect('mongodb://localhost:27017/app1db', options);
mongoose.connection.on('error', function(err: any) {
    console.error('MongoDB connection error: ' + err);
    process.exit(-1);
});
```

## Modelファイルを作成する
models配下に`TestMongoModel.ts`を作成し、以下を記述します。

```ts:TestMongoModel.ts
import mongoose from 'mongoose'; //mongoDBに接続するためのライブラリ
const Schema = mongoose.Schema; //mongoDBのスキーマを作る

const TestMongoSchema = new Schema({
    user :String,
    email: String
},{
    collection: 'test_user'
});

// スキーマをモデルとしてコンパイルし、それをモジュールとして扱えるようにする
module.exports = mongoose.model('TestMongoModel', TestMongoSchema);
```

続いてDBからデータを取得する新規APIの準備をしていきましょう。
routes/index.tsに test/mongo/:user のパスを追加します。

```ts:index.ts
router.get('/test/mongo/:user', (req, res, next) => {
    const { user } = req.params;
    const service = new TestMongoService();

    service
      .run(user)
      .then(result => res.status(200).send(result))
      .catch(next);
});
```

さらにservices配下に`TestMongoService.ts`を作成し、以下を記述します。

```ts:TestMongoService.ts
//インターフェース
export interface ITest {
  user: string;
  email: string;
}

//クラス
export class TestMongoService {
    public async run(params: any): Promise<ITest> {
      const testAll = await TestMongoModel.find();  
      return testAll;
    }
}
```

パスパラメータを活かせてませんが、これで一通りできました。
postmanでhttp://localhost:3000/test/mongo/alluser でAPIを叩くと以下が表示されることを確認できれば成功です！!

```json
[
    {
        "_id": "5f12ec4732c83a71dcf0e7db",
        "user": "user1",
        "email": "user1@gmail.com"
    },
    {
        "_id": "5f12ec5532c83a71dcf0e7dc",
        "user": "user2",
        "email": "user2@gmail.com"
    }
]
```

# 参考資料
https://qiita.com/drafts/73cc7160d002a4989416/edit
https://qiita.com/notakaos/items/3bbd2293e2ff286d9f49