Docker+Express+TypeScript+ts-node-devで環境構築してみた

# 概要
TypeScriptでExpressを利用した開発環境を構築したので、メモとして残します

# 利用ツール
- docker-compose
- Express
- TypeScript
- ts-node-dev

# 手順
## 1. dockerファイルの作成

```yml:docker-compose.yml
version: "3"
services:
  express:
    build: .
    tty: true
    ports:
      - "3000:3000"
    volumes:
      - ./app:/app
```

```Dockerfile:Dockerfile
# 公式イメージ, 安定版, 軽量なalpine版を利用
FROM node:16.14.2-alpine

WORKDIR /app
```

## 2. コンテナ起動

```sh:shell(ホスト)
# イメージのビルドおよび、コンテナ起動
$ docker-compose up -d --build
```

`-d`はバックグランドでコンテナ起動

## 3. 必要なパッケージインストール

```sh:shell(コンテナ)
# コンテナ内に入る
$ docker-compose exec express sh

# yarnの初期化(必要に応じて入力する)
$ yarn init

# パケージの導入
$ yarn add express
$ yarn add --dev @types/express @types/node typescript ts-node-dev
```

- [Express](https://expressjs.com/ja/)
    - Node.jsでAPIなどを簡単に作成できるWebアプリフレームワーク
- TypeScript
    - 静的型付け言語、型安全にしてソースコードを記述できる
- ts-node-dev
    - ts-node, node-devを組み合わせたもの
    - TypeScirptで記述したコードをJavasSriptにコンパイルする必要がなくホットリロードを利用して開発ができるため、必須

## 4. TypeScriptの設定ファイル初期化

これをやらないとエラーになる

```sh:shell
# コンテナ内
# TypeScriptの初期化
$ yarn run tsc --init
```

tsconfig.jsonが作成されるので、必要に応じて設定ファイルを記述してください。

## 5. package.jsonにscriptを追加

これは必須ではありませんが、通常の開発であれば登録します。今回は、`$ yarn dev`で露おーかるサーバを軌道するようにします。

```json:package.json
  "scripts": {
    "dev": "ts-node-dev --respawn src/index.ts"
  },
```

`--respawn`は、今回のサンプルのように本来実行して即終了のプログラムも、終了せず監視状態にする。常時起動の待ち受けサーバなら不要です。
`src/index.ts`は5. で作成するエントリーファイルへのパスです。必要に応じて変更してください。

## 6. エントリーファイルの作成

`src/index.ts`を作成。エントリーファイルの作成場所は問いませんが、4. でのファイルパスへの指定を合わせてください。
適当にレスポンスを返すコードを記載しています。

```ts:src/index.ts
import express from "express";

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

//CROS対応（というか完全無防備：本番環境ではだめ絶対）
app.use(
  (req: express.Request, res: express.Response, next: express.NextFunction) => {
    res.header("Access-Control-Allow-Origin", "*");
    res.header("Access-Control-Allow-Methods", "*");
    res.header("Access-Control-Allow-Headers", "*");
    next();
  }
);

app.listen(3000, () => {
  console.log("Start on port 3000.");
});

type User = {
  id: number;
  name: string;
  email: string;
};

const users: User[] = [
  { id: 1, name: "User1", email: "user1@test.local" },
  { id: 2, name: "User2", email: "user2@test.local" },
  { id: 3, name: "User3", email: "user3@test.local" },
];

//一覧取得
app.get("/users", (req: express.Request, res: express.Response) => {
  res.send(JSON.stringify(users));
});
```

## 7. サーバー起動

```sh:shell(コンテナ内)
$ yarn dev
```

## 8. 確認

```sh:shell(ホスト)
$ curl -s -X GET http://localhost:3000/users
[{"id":1,"name":"User1","email":"user1@test.local"},{"id":2,"name":"User2","email":"user2@test.local"},{"id":3,"name":"User3","email":"user3@test.local"}]
```

となれば、成功です。

index.ts内のファイルを変更して保存するとすぐに反映されることも確認（ホットリロード）できます。

## 9. dockerfileを完成させる


```Dockerfile:Dockerfile
FROM node:16.14.2-alpine

WORKDIR /app

# パッケージインストール
COPY ./app/package.json ./app/yarn.lock ./
RUN yarn install

# ファイル全コピー
COPY ./app .

# ローカルサーバー起動
ENTRYPOINT [ "yarn", "dev" ]
```

これで次回移行`$ docker-compose up -d`でローカルサーバーも起動する。
コンテナを再度作り直しても、`$ docker-compose up -d --build`コマンドを叩くだけで、パッケージインストール、サーバの起動もやってくれるので便利。

ローカルサーバ(express)がログが見たい場合は、`$ docker-compose logs -f express`で見れる。

# 最後に

今回はデータベースは省いています。が、データベースもdocker-composeにて簡単にサーバを起動して連携できます。なので次回また、その方法について、メモ残します。

# 参考
https://qiita.com/zaburo/items/0d47b3c8588bb3f96353

https://qiita.com/sa9ra4ma/items/67ab5ac6fea3e5f065b0

https://qiita.com/zaburo/items/69726cc42ef774990279

https://typescriptbook.jp/reference/tsconfig/tsconfig.json-settings

https://docs.microsoft.com/ja-jp/visualstudio/javascript/compile-typescript-code-npm?view=vs-2022