VScode+Dockerでexpressの開発環境を共有してみた

[前回](https://qiita.com/betarium/items/7921a7ac96e16ae84edb)に続き、expressサーバーの開発環境をDocker+VScode Remote-Containerで構築し開発環境を共有してみた。

:::note
[前回：VScode+Dockerでreactの開発環境を共有してみた](https://qiita.com/betarium/items/7921a7ac96e16ae84edb)
:::

https://qiita.com/betarium/items/7921a7ac96e16ae84edb

これで、1つのプロジェクトでフロントエンドとバックエンド両方の開発ができるようになる。

## express環境作成

まずはexpressのスケルトンを```express-generator```コマンドで作成する。
ただし、express-generatorはtypescriptを使用してないので```tsc```コマンドで設定ファイルを追加する。
vscodeで[前回のプロジェクト](https://qiita.com/betarium/items/7921a7ac96e16ae84edb)を開き、ターミナルで以下のコマンドを実行する。

```sh
pnpx express-generator back

cd back

pnpm install typescript@4.7.4

pnpm tsc --init

echo node_modules/ >> .gitignore
echo build/ >> .gitignore
```

必要なパッケージを追加する。

```sh
pnpm install -D @types/node
pnpm install -D @types/express

pnpm install -D ts-node
pnpm install -D ts-node-dev
pnpm install -D tsconfig-paths
pnpm install -D cross-env
```
## ディレクトリ構成

ディレクトリ構成を変更する。
デフォルトだとsrcディレクトリがないので、ソースコードはsrcディレクトリに移動する。
ファイルの拡張子は.tsに変更する。

```sh
mkdir src
mkdir src/routes

mv routes/index.js src/routes/index.ts
mv routes/users.js src/routes/users.ts
mv app.js src/app.ts
mv bin/www src/main.ts

rmdir bin
rmdir routes
```

ディレクトリ構成の変更後は以下のファイル配置になる。
```
/workspace/back
|--.gitignore
|--build
|--package.json
|--public
|--src
|  |--app.ts
|  |--main.ts
|  |--routes
|  |  |--index.ts
|  |  |--users.ts
|--tsconfig.json
|--views
```

## typescript対応

typescriptにするとビルドが通らなくなるので設定ファイルとコードを修正する。

コードの修正が必要な個所は以下がある。

* typescriptでは変数の型の指定がないとエラーになる場合があるので、型を明示的に指定。
* requireを```import xxx from "mypackage"```のようにしないとvscodeの補完が効かないので修正。
* ほかのコードをimportするのに、import対象のコードで```export xxx```のようにエクスポートするクラス・変数を指定する必要がある。

### package.json

npmのスクリプトを追加する。
これで```pnpm start```のようなコマンドが使えるようになる。

```json
  "scripts": {
    "start": "env NODE_PATH=./build node ./build/main.js",
    "debug": "ts-node-dev -r tsconfig-paths/register ./src/main.ts",
    "build": "tsc"
  },
```

### tsconfig.json

ディレクトリ構成の変更に合わせて設定を変更する。

```json
    "rootDir": "./src",
    "baseUrl": "./src",
    	"paths": {
    		"*": [
    			"./*"
    		],
    },
    "outDir": "./build",
```

### src/app.ts

requireをtypescript準拠のimportに修正する。
```typescript
//var express = require('express');
import express from 'express';
```

ディレクトリ構成に合わせてパスを修正する。
```typescript
app.set('views', path.join(__dirname, '../views'));

app.use(express.static(path.join(__dirname, '../public')));
```

型を指定してないとエラーになるので修正。
```typescript
//app.use(function (err: any, req: express.Request, res: express.Response, next: express.NextFunction) {
app.use(function (err: any, req: express.Request, res: express.Response, next: express.NextFunction) {
```

importで読み込めるように修正。
```typescript
//module.exports = app;
export default app
```

### src/main.ts

ディレクトリ構成に合わせてimportを修正。
```typescript
//var app = require('../app');
import app from 'app'
```

型の指定がないとエラーになるので修正。
```typescript
//function normalizePort(val) {
function normalizePort(val: string) {

//function onError(error) {
function onError(error: NodeJS.ErrnoException) {
```

### src/routes/index.ts

importを修正。
```typescript
//var express = require('express');
import express from 'express';
```

### src/routes/users.ts

index.tsと同様にimportを修正。
```typescript
//var express = require('express');
import express from 'express';
```

## 動作確認

設定が終わったらターミナルでビルドしexpressを起動する。

```sh
pnpm build

pnpm start
```

expressが起動したらブラウザで http://localhost:3000/ にアクセスしてExpressの画面が表示されれば成功である。

![express-3000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/372771/a7c5fe23-007a-1e22-57f4-7c4bad0f7831.png)

開発用に、typescriptのままビルドせずに起動することもできる。
```sh
pnpm debug
```

## ポートの変更

expressとviteを同時に起動できるようにポートを変更する。
また、スクリプトとタスクを追加し開発しやすくする。

### src/main.ts

express-generatorで生成したコードはデフォルトではtcpポート3000を使用するが、これだとreact(vite)の使用するポートと重複するため同時起動できないので、expressのポートを4000に変更する。

```typescript
//var port = normalizePort(process.env.PORT || '3000');
var port = normalizePort(process.env.PORT || '4000');
```

### .devcontainer/docker-compose.yml

新しいポートを使うためdockerの設定も追加する。

```yaml
    ports:
      - 3000:3000
      - 4000:4000 #追加
```


## タスクの追加

expressとviteを同時に起動できるようにスクリプトとタスクを追加し開発しやすくする。

### .devcontainer/docker-entry.sh

環境を共有しやすいようにdocker起動時の初期化スクリプトを追加する。
起動時に```pnpm install```を実行するので、すぐにサーバを起動できるようになる。
vscodeでdockerの環境を使用するため、スクリプトの最後には```while sleep 1000; do :; done```を追加しコンテナが終了しないようにする。

```sh
#!/bin/sh

cd /workspace/front

pnpm install --frozen-lockfile

cd /workspace/back

pnpm install --frozen-lockfile

cd /workspace

while sleep 1000; do :; done
```

### .devcontainer/docker-compose.yml

起動時のコマンドで初期化スクリプトを実行するようにする。

```yaml
    command: docker-entry.sh
```

### .devcontainer/Dockerfile


初期化スクリプトをdockerに追加する。

```docker
COPY docker-entry.sh /usr/bin/
RUN chmod +x /usr/bin/docker-entry.sh
```

### .vscode/launch.json

毎回コマンドで起動するのは面倒なので、vscodeから実行できるようにする。
```json
{
    "version": "0.2.0",
    "configurations": [
        {
            "command": "pnpm dev",
            "name": "Debug Front",
            "request": "launch",
            "type": "node-terminal",
            "cwd": "${workspaceFolder}/front"
        },
        {
            "command": "pnpm debug",
            "name": "Debug Server",
            "request": "launch",
            "type": "node-terminal",
            "cwd": "${workspaceFolder}/back"
        },
    ]
}
```

### 起動

一度vscodeでプロジェクトを閉じた後、dockerのコンテナとイメージを削除する。

:::note
dockerの削除にはdockerプラグインをインストールしておくと便利である。
https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-docker

左のCONTAINERSとIMAGESから対象を選択し、右クリックして削除できる。

![docker.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/372771/a2e13182-4c1f-66ed-f36b-d5117ed55a69.png)
:::

vscodeで再度プロジェクトを開くと設定変更を反映したプロジェクトが起動する。

左のアイコンからRun and Debugを選択し、左上の起動するコマンドを選んで『▶』を押すと実行できる。

![start.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/372771/70605bf7-cf77-9a63-2d22-f2052e43d4ff.png)

Debug FrontとDebug Serverの両方を起動し、ブラウザで http://localhost:3000/ と http://localhost:4000/ の両方が開いて表示できれば成功である。

![vite.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/372771/c809171b-f8f0-1311-cf00-905bec0408ea.png)

![express-4000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/372771/b9d61f53-c6ac-a06f-1a86-311c27468c74.png)

## サンプルコード
[https://github.com/betarium/sample-react-express/tree/0.2.0](https://github.com/betarium/sample-react-express/tree/0.2.0)