fragmentary notesITエンジニアの気まぐれメモ

【備忘】Express.js + PostgreSQLをDocker上に構築2 - TypeScript版

Introduction

以前、Docker上にExpress.jsとPostgreSQLを用いてWebAPIを作成するためのベースを構築する手順をまとめました。

PV(Page View)はさっぱりですが、個人的には毎回 ここを読み直すことでスムーズな作業ができているので、備忘録としてOutputを残すことが非常に有効だと実感しました。そんなわけで同じテーマの第二弾、TypeScriptでExpress.js + PostgreSQLのWebAPIのベースを構築する手順を書き記してみようと思います。

前回との違いは大きく2点、言語を純粋なJavaScriptから型の概念を持つ上位拡張言語TypeScriptに、ORM(Object-relational mapping)をSequelizeからPrismaに、それぞれ変更しています。

平日の夜、忘れないうちにと速足で書き留めているため、いつも以上に文章が雑、かつ誤植もあるかと。。。後で読み返して必要に応じて修正します(_ _))ペコッ

前置き

環境

今回は、Windows11のWSL2上にインストールしたUbuntu 22.04.3 LTS を使用し、その上でdockerを起動・各種アプリケーションを動かします。WSL2やdocker等の基盤の環境設定手順は割愛。

ワークフォルダ

WSL2 Ubuntu上に下記構成のフォルダを用意します。

- express_on_typescript
  - node
    - app
  - postgresql
    - psdata

完成品

GitHub : express_on_typescript

dockerコンテナ作成

dockerfile作成

node.js環境用Dockerfileを作成

express_on_typescript/node/ に、node.js用のコンテナの元となるDockerfileを作成します。

FROM node:lts-alpine

ENV NODE_ENV=development

WORKDIR /app

RUN apk update && \
    apk add git && \
    apk add openssh && \
    npm install -g express-generator

ENV HOST 0.0.0.0
EXPOSE 3000

PostgreSQL環境用Dockerfileを作成

同様に、PostgreSQL用のDockerfileを、express_on_typescritp/ に作成。

FROM postgres:16.2
RUN localedef -i ja_JP -c -f UTF-8 -A /usr/share/locale/locale.alias ja_JP.UTF-8
ENV LANG ja_JP.utf8

docker-compose作成

docker-compose定義作成

node.jsとPostgreSQLのコンテナをまとめて管理するため、docker-compose定義を、express_sandbox/ に作成。サービスの名称として、node.jsコンテナを webserver、PostgreSQLコンテナを db とします。

またデータの永続化のため、nodeは、/app配下を最初に作成したホスト上の/node/appへ、PostgreSQLは、/var/lib/postgresql/data配下を/postgresql/psdata にマウントします。この設定をしておかないとコンテナを終了した際に、データが消えてしまうため。

version: '3'

services:
  webserver:
    build: node
    tty: true
    volumes:
      - .:/workspace:cached
      - ./node/app:/app
    ports:
      - "3000:3000"
      - "5555:5555"
  
  db:
    build: postgresql
    volumes:
      - ./postgresql/psdata:/var/lib/postgresql/data
    ports:
      - "5432:5432"
    environment:
      POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --locale=ja_JP.UTF-8"
      POSTGRES_PASSWORD: 'password'

あわせて、この後コンテナをVSCodeで開いた際に、ルートフォルダ(express_on_typescript)を参照できるよう、ホスト側のルート . を、コンテナ側の/workspaceにマウントしておきます。この経緯は下記参照。

docker-composeからビルド・起動

作成したコンテナ定義から、ビルドする。WSL2のターミナル上から express_on_typescritp/ にて下記コマンド実行。

docker-compose build

ビルド完了後、コンテナを起動します。

docker-compose up -d

docker-compose ps から起動中のコンテナを確認し、2つのコンテナが起動していればOK。

docker-compose ps

NAME                                IMAGE                             SERVICE             STATUS       
express_on_typescript-db-1          express_on_typescript-db          db                  Up 36 seconds
express_on_typescript-webserver-1   express_on_typescript-webserver   webserver           Up 36 seconds

Express.jsサンプルプロジェクト作成

事前準備

WSL2上から下記コマンドを実行することで、起動中のnode.jsコンテナに入ります。以降はコンテナ上での作業となります。

docker-compose exec webserver /bin/sh

コンテナに入り込むことができたら、app直下に、nodeプロジェクトの初期化実行します。

npm init -y

プロジェクトの初期化が完了したら、続いて必要なパッケージを順に追加します。まずはepress.js関連のパッケージを追加。

npm install express cors dotenv

続いて、TypeScript開発関連のパッケージを追加。

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

最後に、TypeScriptの初期設定をします。下記コマンドを実行。

npx tsc --init

tsconfig.jsonという設定ファイルが作られるので、これを開き下記1文を追加します(コメントアウトされていると思うので、outDirで検索してみると見つかります)。これはTypeScriptをコンパイルした後のJavaScriptをどこに出力するかという定義になります。今回はapp配下の/distを指定します。

"outDir": "./dist",

動作確認

ここまででTypeScript版Express.js環境が構築できたので、お決まりの"Hello World!"タイムといきましょう。express_on_typescript/app配下にapp.tsを追加、下記コードを記述します。

import express, { Request, Response } from "express";
import dotenv from "dotenv";
import cors from "cors";

dotenv.config();
const app = express();
app.use(cors());

const PORT = 3000;

app.get('/', (req, res) => { res.json({title : "Hello World, express.js on TypeScript"}); });
app.listen(PORT, () => { 
  console.log("Server running at PORT: ", PORT);
});

TypeScriptの最も基本的な実行方法は、一度 記述したソースコードをコンパイルし、JavaScriptのファイルを作成し、そのJavaScriptファイルから実行する必要があります(この手順を簡略化する方法は後述)。

まずはTypeScript→JavaScriptのコンパイルをしましょう。下記コマンドを実行します。

npx tsc

正常終了すると、/dist配下にapp.jsが作られていると思います。こいつを実行してみましょう。

node ./dist/app.js

ポート:3000 でプログラムが起動しているはずなので、WEBブラウザから http://localhost:3000/ にアクセス、Hello World とjson形式でお返事してくれていればOK。

TypeScriptでExpress.jsを動かすことができました。でも毎回コンパイルして、jsファイルから起動するのはめんどくさいですよね。自動でコンパイルし、起動しなおすことなく自動反映してほしい。ということで少し工夫しましょう。

まずjsファイルを作成しなくてもファイルを実行できるコマンドとして ts-node があります(最初にパッケージ追加しましたね)。これを使えば、tsファイルから直セルアプリケーションを起動することができます。

続いて、変更を自動で検知し、再読み込みしてくれるコマンド、nodemon。これがあれば変更都度再起動する必要がなくなります。

この2つを組み合わせて、実行用スクリプトを作成しましょう。/app配下のpackage.jsonを開きます。ここのscript部分に下記startスクリプトを追加します。

  "scripts": {
    "start": "nodemon app.ts"
  },

この状態でstartスクリプトを起動します。このスクリプト1発で先ほど同様アプリケーションが立ち上がるようになりました。

npm start

PostgreSQLとの接続

続いて、PostgreSQLと接続し、DBから取得した内容をもとにお返事できるようにします。前回は、ORM(Object-relational mapping)としてSequelizeを使用しましたが、今回はPrismaを使用します(SequelizeとTypeScriptの組み合わせは情報が少なく、動かせなかったため)。

Prismaの追加 & 初期設定

最初にPrismaのパッケージを追加します。

npm install prisma @prisma/client

次にtsconfig.jsonに下記1文を追加します。

"lib": ["esnext"],

ここから初期設定をしていきます。初期化コマンドを実行。

npx prisma init

.envファイルが作られ DATABASE_URL が初期設定されていると思います。ここにPostgreSQLの接続情報を記述します。形式は、

postgresql://USER:PASSWORD@HOST:PORT/DATABASE

今回、dockerの設定として

  • DBユーザ:postgres
  • DBユーザPW:password
  • HOST:docker-composeのサービス名 db
  • PORT:5432
  • DB名:postgres

として定義しているので、下記のように修正します。

DATABASE_URL="postgresql://postgres:password@db:5432/postgres"

これで初期設定は完了。次に実際にデータモデルを定義、migrationを実行しDBへ定義を反映してみます。

データモデルの定義

DBへのテーブル登録と対応するプログラム側のデータモデル登録のためのマイグレーションスクリプトを生成します。

今回はお試しということで、姓・名・メールアドレスを管理するモデル:ApUser を作成してみます。

/app/prisma配下に作成された schema.prisma を開きましょう。ここにモデルの定義を追記していきます。下記記述を追加しましょう。

model ApUser {
  id        Int @default(autoincrement()) @id
  firstName String?
  lastName  String?
  email     String?
  disabled  Boolean   @default(false)
  createdAt DateTime  @default(now()) @map("created_at")
  updatedAt DateTime  @updatedAt @map("updated_at")
}

ここからmigrationスクリプトを作成・実行するコマンドを流します。

npx prisma migrate dev --name init

正常終了するとmigrationファイルが作成されると同時に、DBにもモデルが反映されます。試しにDBの中を覗いて(私はクライアントソフトとしてA5:SQLを使用)、テーブルが作られていたらOK。

この後のテスト用に数件、適当なレコードを追加します。

ちなみに、

npx prisma studio

を実行すると、http://localhost:5555 からモデルの参照・編集が可能なブラウザアプリが起動します。ここからレコードを追加することも可能。

データ取得プログラム動作確認

最後に、Express.jsが、PostgreSQLのDBから取得した内容をもとにお返事してくれるようプログラムを改修し、Express.js + PostgreSQLサンプルプログラムの動作を確認します。

import express, { Request, Response } from "express";
import dotenv from "dotenv";
import cors from "cors";
import { PrismaClient } from "@prisma/client";

dotenv.config();
const app = express();
app.use(cors());

const PORT = 3000;

const prisma = new PrismaClient();

app.get('/', (req, res) => { 
  res.json({title : "Hello World, express.js on TypeScript"});
});

app.get('/users', (req, res) => {
  prisma.apUser.findMany({
    where:{
      disabled : false
    }
  }).then((users)=>{
    res.json(users);
  })
})

app.listen(PORT, () => { 
  console.log("Server running at PORT: ", PORT);
});

この状態で、http://localhost:3000/users にアクセスし、DBに登録した値がレスポンスとしてかえってきていれば成功です!!

終わりに

忘れないうちに、連休の最後に試した内容を走り書きしてみました。1日前にやったことなのに、再度手順をなぞろうとすると既に忘れていること多々で、WEBブラウザの履歴をあさる羽目になりました。連休明けの平日の夜、頑張って書いてみてよかったと思えるときがいずれ来ることを祈り。

参考文献

Express(Node.js)でTypeScript環境を構築するための完全ガイド - REFFECT

TypeScriptでORMのPrismaに入門した - Qiita

  • Home
  • /
  • Posts
  • /
  • 【備忘】Express.js + PostgreSQLをDocker上に構築2 - TypeScript版
Tech-TIPS

Comments

© 2024 shunya_wisteria