🐳 DockerでTypeScript + Expressサーバーを作ってみる

こんにちは、さるまりんです🐒

今回はTypeScript + Expressでサーバーサイドアプリケーションを作り、Docker上に開発環境を構築する手順をメモします。

TypeScriptが初めてでも、そのまま使えるように進めていきます!

なぜExpressを使うの?

ExpressはNode.js上で動く軽量で柔軟なWebフレームワークです。最近周りに使っている人がいるのでやってみたいと思って今回のチャレンジです。

こんな感じで簡単にHello, world!できるようです。

// ExpressでHello World
import express from 'express';
const app = express();
app.get('/', (req, res) => res.send('Hello, world!'));
app.listen(3000);

プロジェクト構成

project-root/
├── docker-compose.yml
├── Dockerfile
├── package.json
├── tsconfig.json
├── src/
│ └── index.ts

ステップ1:プロジェクト初期化

mkdir my-ts-server && cd my-ts-server
npm init -y

ステップ2:依存パッケージのインストール

npm install express
npm install --save-dev typescript ts-node-dev @types/node @types/express

以下が実際に実行したものの抜粋です。

% npm install express

added 66 packages in 4s

% npm install --save-dev typescript ts-node-dev @types/node @types/express

added 72 packages, and audited 139 packages in 9s

ステップ3:TypeScript設定(tsconfig.json)

{
  "compilerOptions": {
    "target": "ES2020",
    "module": "CommonJS",
    "rootDir": "src",
    "outDir": "dist",
    "strict": true,
    "esModuleInterop": true
  }
}

ステップ4:Expressサーバーを作る(src/index.ts)

import express from 'express';

const app = express();
const port = process.env.PORT || 3000;

app.use(express.json());

app.get('/', (req, res) => {
  res.send('Hello from TypeScript + Express!');
});

app.listen(port, () => {
  console.log(`Server is running on port ${port}`);
});

🐳 ステップ5:Dockerfileの作成

FROM node:18
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
CMD ["npx", "ts-node-dev", "--respawn", "src/index.ts"]

🐳 ステップ6:docker-compose.yml を用意

version: '3.8'
services:
  app:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - /app/node_modules

起動と確認

docker-compose up --build

注意:mac上で実行したのですが、実際のコマンドはちょっとだけ違っていて、

docker compose up --build

で動きました。

以下のそログの抜粋です。

% docker compose up --build
Compose can now delegate builds to bake for better performance.
 To do so, set COMPOSE_BAKE=true.
[+] Building 81.9s (12/12) FINISHED                                                                                             docker:desktop-linux
 => [app internal] load build definition from Dockerfile                                                                                        0.0s
 => => transferring dockerfile: 180B                                                                                                            0.0s
 => [app internal] load metadata for docker.io/library/node:18                                                                                  2.5s
 => [app auth] library/node:pull token for registry-1.docker.io                                                                                 0.0s
 => [app internal] load .dockerignore                                                                                                           0.0s
 => => transferring context: 2B                                                                                                                 0.0s
 => [app 1/5] FROM docker.io/library/node:18@sha256:867be01f97d45cb7d89a8ef0b328d23e8207412ebec4564441ed8cabc8cc4ecd                           72.6s
 => => resolve docker.io/library/node:18@sha256:867be01f97d45cb7d89a8ef0b328d23e8207412ebec4564441ed8cabc8cc4ecd                                0.0s
 => => sha256:e56a6e3bc5724c51df0a1ad86c6e38aee233bb343457e98c943c3c7c1c9a2a84 6.39kB / 6.39kB                                                  0.0s
 => => sha256:cf05a52c02353f0b2b6f9be0549ac916c3fb1dc8d4bacd405eac7f28562ec9f2 48.49MB / 48.49MB                                                8.9s
 => => sha256:867be01f97d45cb7d89a8ef0b328d23e8207412ebec4564441ed8cabc8cc4ecd 6.41kB / 6.41kB                                                  0.0s
 => => sha256:a0ee20d21ff7e3199f16644fd076ef1435b42689ca015daf08e47c02fd6cfc38 2.49kB / 2.49kB                                                  0.0s
 => => sha256:63964a8518f54dc31f8df89d7f06714c7a793aa1aa08a64ae3d7f4f4f30b4ac8 24.01MB / 24.01MB                                                6.1s
 => => sha256:ca513cad200b13ead2c745498459eed58a6db3480e3ba6117f854da097262526 64.39MB / 64.39MB                                                9.8s
 => => sha256:c187b51b626e1d60ab369727b81f440adea9d45e97a45e137fc318be0bb7f09f 211.36MB / 211.36MB                                             51.3s
 => => sha256:68b421cb119c75992bb4a52f93da195c7f10de1cf73fdaf9ea82af9dd008e42a 3.32kB / 3.32kB                                                  9.1s
 => => extracting sha256:cf05a52c02353f0b2b6f9be0549ac916c3fb1dc8d4bacd405eac7f28562ec9f2                                                       7.0s
 => => sha256:bf3cd053bbe4b167d7cef55cc32347e55dd140d818cbf4a4bfca50d612fcda39 45.68MB / 45.68MB                                               19.9s
 => => sha256:e1dbe5f455cde9503d18d04e9a2c641b5aff12cacfa7a3ec093986c828ea178c 1.25MB / 1.25MB                                                 10.6s
 => => sha256:a63cf41c258c6ef4426dea1e73a875cfc00d43547f2fa6085ad9b2077f3197a5 447B / 447B                                                     10.9s
 => => extracting sha256:63964a8518f54dc31f8df89d7f06714c7a793aa1aa08a64ae3d7f4f4f30b4ac8                                                       1.6s
 => => extracting sha256:ca513cad200b13ead2c745498459eed58a6db3480e3ba6117f854da097262526                                                       6.5s
 => => extracting sha256:c187b51b626e1d60ab369727b81f440adea9d45e97a45e137fc318be0bb7f09f                                                      15.3s
 => => extracting sha256:68b421cb119c75992bb4a52f93da195c7f10de1cf73fdaf9ea82af9dd008e42a                                                       0.0s
 => => extracting sha256:bf3cd053bbe4b167d7cef55cc32347e55dd140d818cbf4a4bfca50d612fcda39                                                       4.7s
 => => extracting sha256:e1dbe5f455cde9503d18d04e9a2c641b5aff12cacfa7a3ec093986c828ea178c                                                       0.1s
 => => extracting sha256:a63cf41c258c6ef4426dea1e73a875cfc00d43547f2fa6085ad9b2077f3197a5                                                       0.0s
 => [app internal] load build context                                                                                                           1.7s
 => => transferring context: 31.97MB                                                                                                            1.6s
 => [app 2/5] WORKDIR /app                                                                                                                      1.1s
 => [app 3/5] COPY package*.json ./                                                                                                             0.0s
 => [app 4/5] RUN npm install                                                                                                                   4.0s
 => [app 5/5] COPY . .                                                                                                                          1.0s 
 => [app] exporting to image                                                                                                                    0.6s 
 => => exporting layers                                                                                                                         0.6s 
 => => writing image sha256:47cb3af8398cb2578c6c6a113afdb6b454f9661207764ee901094e7ca4473c1d                                                    0.0s 
 => => naming to docker.io/library/my-ts-server-app                                                                                             0.0s 
 => [app] resolving provenance for metadata file                                                                                                0.0s
[+] Running 3/3
 ✔ app                           Built                                                                                                          0.0s 
 ✔ Network my-ts-server_default  Created                                                                                                        0.1s 
 ✔ Container my-ts-server-app-1  Created                                                                                                        0.4s 
Attaching to app-1
app-1  | [INFO] 03:47:03 ts-node-dev ver. 2.0.0 (using ts-node ver. 10.9.2, typescript ver. 5.8.3)
app-1  | Server is running on port 3000

ブラウザでhttp://localhost:3000にアクセスすると:

Hello from TypeScript + Express!と表示されていますね。

curlでAPIを叩く確認

curlでも叩いてみます。

↓のようになりました。

% curl http://localhost:3000/
Hello from TypeScript + Express!%

大丈夫そうですね。

ts-node-devにより、コードを編集すると自動でサーバーが再起動されます。これで開発中のホットリロードもOKです!
環境変数やDBとの接続を追加すれば、そのまま使える構成になるかな。

TypeScript + Expressよく使われるようになってきてるみたいですね。
Docker化すれば、どの環境でも同じ動作を再現できます。
さらにこの先のこともできるようにしていきたいと思います。
いろいろやるならTypeScriptのカテゴリー作ろうかな?

読んでくださってありがとうございました。
それではまた!