Node.js + Express + TypeScriptでWebAPIを作る（1/4）

## 概要

* メモ的な書き残し
* 環境構築

## 環境

macOS Mojave(10.4)

### Node.js用意

home brewで管理していたnodeを削除して最新版を入れ直し。

```
brew list | grep node
brew uninstall node
brew install node
```

2018/12/23時点で最新Node.jsはv11.5.0です。同梱npmはv6.4.1になります。とりあえずこれで進めていきます。バージョン依存するようなことは基本的にしないのでNode.js[10.9.0]/npm[6.2.0]やNode.js[10.2.1]/npm[5.6.0]とかでも多分大丈夫。

### MongoDB

もともとhome brew経由で入れてありましたが、upgradeしておきます。

```
brew upgrade mongodb
```
インストールしていない場合は

```
brew install mongodb
```

これでおｋ。2018/12/23時点で最新はv4.0.4です。404って何か縁起良くない感じがする。

### Visual Studio Code

開発環境はVSCode（かVim）を推しておきます。VSCodeを推す理由はプラグインとMSへのサインインだけで画面共有してペアプロができることです。多分複数人数でもできるのでモブプロもできるはず。VSCode + Teamsで面白いコラボレーションできそう。
今回はVSCodeを使うので説明は一部VSCodeありきになります。

### initとパッケージ追加とちょっとした各種設定

プロジェクトディレクトリ作ってちょこちょここちょこちょします。  
npm installする時に--save-exactオプションを付けてバージョンを固定した方が良いと思います。ちなみに今回はしません。

```
mkdir js-web-api
cd js-web-api
npm init
npm install express body-parser mongoose jsonwebtoken --save
npm install --save-dev typescript ts-node
./node_modules/.bin/tsc --init
```

tsconfig.jsonを編集します。デフォルトから以下の内容だけ編集します。

```
{
"sourceMap": true,
"outDir": "build",
},
"include": [
  "src/**/*",
  "test/**/*"
]
```

src配下にex.tsを作成して以下のコードを追加。

```
const msg: string = "hoge";
console.log(`${msg} piyo`);
```

tscを実行

```
./node_modules/.bin/tsc
```

buildディレクトリにjsファイルとmapファイルができればOK。nodeでex.jsを実行する。

```
node ./build/ex.js
result > hoge piyo
```

terminalにhoge piyoと表示されればOK。ts-nodeを使えばコンパイル前のTypeScriptを実行できます。

```
./node_modules/.bin/ts-node ./src/ex.ts
result > hoge piyo
```

./node_modules/.bin/を打つのがどちゃくそめんどくさくてハゲそうという方はnpm-runをインストール（npm install -g npm-run）しておくと良いと思います。それかnpm scriptsに

```
"scripts": {
  "ts-node": "./node_modules/.bin/ts-node",
  "tsc": "./node_modules/.bin/tsc"
},
```

こんな感じで書いておくと良いと思います。実行は`npm run ts-node ./src/ex.ts`ってな感じです。
続いてLintを入れていきます。IDEに準じたLintでも良いんですけど環境が変わったら警告が出るってのも気持ち悪いので。

```
npm install --save-dev tslint
```

今回はVSCodeを使っているのでTSLint用extensionを追加します。

```
code --install-extension eg2.tslint
```

TSLintを有効化するために設定ファイルを作ります。

```
./node_modules/.bin/tslint --init
```
ちなみに手書きで作成しても問題ありません。作成が終わったらdefaultSeverityの値を"error"から"warn"に変更しておきます。
コマンドパレット（cmd+p）に`configure task`を入力して`テンプレート->Other`と選択します。taskの内容は./.vscode/task.jsonのtasksに入力していきます。コマンドパレットに`task `（後ろに半角）を入力すると構成済みタスクの一覧が出てきますのでそちらから実行可能です。

```
{
  "label": "ts-node",
  "type": "shell",
  "command": "./node_modules/.bin/ts-node ${relativeFile}"
}
```

こんな感じで構成してTSファイルをアクティブにしてts-node taskを実行すると`./node_modules/.bin/ts-node ./src/ex.ts`と同じ実行結果を得られます。task楽ちんですね。

ユニットテストについても環境を整えて行きます。

```
npm install --save-dev @types/node espower-typescript power-assert mocha @types/mocha
```

ex.tsを以下のように書き換え。

```
export function ex(msg: string) {
    console.log(`${msg}`);
    return `${msg} piyo`;
}
```

testディレクトリ配下にex.test.tsを作成して以下を記述。

```
import assert = require("assert");
import { ex } from "../src/ex";

describe("ex module", () => {
    const msg = "hoge";
    it("ex method", () => {
        assert(ex(msg) === "hoge piyo");
    });
});
```

tasksに以下を追加

```
{
  "label": "mocha",
  "type": "shell",
  "command": "./node_modules/.bin/mocha -r espower-typescript/guess ${relativeFile}"
}
```

task(mocha)を実行するとテストが走ればOK。  
nycを利用してテストカバレッジを出力します。

```
npm install --save-dev nyc
```

インストールが終わったらpackage.jsonのscriptと同階層に以下を追加

```
"scripts": {},
"nyc": {
  "include": [
    "src/**/*.ts"
  ],
  "extension": [
    ".ts"
  ],
  "require": [
    "ts-node/register"
  ],
  "reporter": [
    "text",
    "text-summary",
    "html"
  ],
  "sourceMap": true
}
```

追加したら、

```
./node_modules/.bin/nyc ./node_modules/.bin/mocha test/*.ts
```

で実行。結果はterminalに出力されます。もう少しわかりやすいようにex.tsを以下のように書き換えます。

```
export function ex(flg: boolean) {
    return (flg === true) ? "isTrue" : "isFalse";
}
```

ex.test.tsを以下のように書き換えます。

```
import assert = require("assert");
import { ex } from "../src/ex";

describe("ex module", () => {
    it("isTrue test", () => {
        assert(ex(true) === "isTrue");
    });
});
```

書き換えたら先程のカバレッジ出力のコマンドを実行。Branchが50%になりました。続いてisFalseのテストを追加。

```
import assert = require("assert");
import { ex } from "../src/ex";

describe("ex module", () => {
    it("isTrue test", () => {
        assert(ex(true) === "isTrue");
    });
    it("isFalse test", () => {
        assert(ex(false) === "isFalse");
    });
});
```

Branchが100%になりましたね。カバレッジを意識してテストコードを書くと品質の担保に繋がるので意識したいところですね。PCにPHPが入っている（terminalで`php -v`と打ってバージョンが返ってくればインストール済み）場合、coverageディレクトリに移動して`php -S localhost:8080`と打ってWebサーバをおっ立ててブラウザから`localhost:8080`にアクセスすると結果が表示されます。

![image-20181223163305167.png](https://qiita-image-store.s3.amazonaws.com/0/298815/bb82c927-4352-bfc0-9bed-3f5b48f09734.png)


見辛いと思いますが、こんな感じ。

ここで一回、今まで使ったコマンドをnpm scriptsかVS Tasksに登録しておきましょう。VS TasksはIDEに依存するのでnpm Scriptsの方が良いかと思います。細かい扱い方や挙動の違い、使えるパラメータ等はググれば出てくるのでそちらでお願いします。細かいところを全部説明し切れないというか把握していないので。

環境がある程度整ったところでログ機能を用意していこうと思います。エラー設計は...うん、後で実装しながら考える（ダメなやつ）。

```
npm install --save log4js @types/log4js
```

log4jsのインストールが終わったらex.tsを以下のように編集。

```
import * as log4js from "log4js";
const log = log4js.getLogger();
log.level = "warn";
log.fatal("This is fatal.");
log.error("This is error.");
log.warn("This is warn.");
log.info("This is info.");
log.debug("This is debug.");
log.trace("This is trace.");
export function ex(flg: boolean) {
    return (flg === true) ? "isTrue" : "isFalse";
}
```

編集したら動かしてみる。

```
npm run ts-node ./src/ex.ts
```

指定したlog.level以上を対象に出力されてるのが確認できる。開発レベルならこれでOKですが、リリースを考えるとファイルに出しておかないとダメ。

```
import * as log4js from "log4js";
log4js.configure({
    appenders: {
        system: { type: 'file', filename: 'system.log' }
    },
    categories: {
        default: { appenders: ['system'], level: 'debug' }
    }
});
const log = log4js.getLogger();
log.fatal("This is fatal.");
:
:
```

一旦こんな感じで再度実行。ルートディレクトリにログができたのでとりあえずOK。続いてログの種類を切り分けてみる。Webで考えるとアクセスログとシステムログ（ロジック側のログ）で切り分ける感じ。

```
import * as log4js from "log4js";
log4js.configure({
    appenders: {
        access: {
            type: "file", filename: "access.log"
        },
        system: {
            type: "file", filename: "system.log"
        }
    },
    categories: {
        access: {
            appenders: ["access"], level: "debug"
        },
        default: {
            appenders: ["system"], level: "debug"
        }
    }
});
const accessLog = log4js.getLogger("access");
const systemLog = log4js.getLogger("system");
accessLog.fatal("This is fatal.");
systemLog.error("This is error.");
accessLog.warn("This is warn.");
systemLog.info("This is info.");
accessLog.debug("This is debug.");
systemLog.trace("This is trace.");
```

こんな感じで切り分けは可能。ファイルも別々になる。Expressでアクセス受けた時にログを出す時は`app.use(log4js.connectLogger(accessLog));`みたいにbindしてあげれば良さそう。

```
access: {
    type: "dateFile", filename: "access.log", pattern: "-yyyyMMdd"
},
system: {
    type: "file", filename: "system.log", maxLogSize: 1048576, numBackups: 5
}
```

日時やサイズ、バックアップ数はこんな感じで指定可能。コード上に設定を書くのは何というかお行儀が良い悪いじゃなくて気にしなくて良いものを乗せておくのが嫌なのです派なので外部に出しておく。プロジェクトルートディレクトリ直下にlog4js.config.jsonを作成して、

```
{
    "appenders": {
        "access": {
            "type": "dateFile",
            "filename": "./log/access.log",
            "pattern": "-yyyyMMdd"
        },
        "system": {
            "type": "file",
            "filename": "./log/system.log",
            "maxLogSize": 1048576,
            "numBackups": 5
        }
    },
    "categories": {
        "access": {
            "appenders": [
                "access"
            ],
            "level": "debug"
        },
        "default": {
            "appenders": [
                "system"
            ],
            "level": "debug"
        }
    }
}
```

こう書いてさっき設定を直で書いていた部分を

```
log4js.configure('./log4js.config.json');
```

にして出来上がり。