【Node.js】バックエンドとフロントエンドとで共通のライブラリを使う

こんにちは、フリーランスエンジニアの太田雅昭です。

現在StrapiとNext.jsとでシステムを組んでいるのですが、Socket通信で使うための型定義ファイルを共有化したいなと思い色々試行錯誤しました。

通常は単純にローカルで参照すればいい話なのですが、Dockerを使っていることもあり、また将来サーバーを分けるとなった時のことを考えると単純ではないなと。

方法

色々方法があるようです。通常ならローカルパスを指定してimportすれば良いかと思います。そのほかにGithubでプライベートでnpmを登録したりする方法もあるようですが、今回は見送りました。複数人で分担するようになったら、Githubでのプライベートnpmも考えたいと思います。

今回、それぞれのDockerイメージに無理やり共通化ライブラリを入れました。

Dockerの仕様上親Dockerfileの親を参照できないことから、親で実行するといった技を使っています。そしてそこからローカルでimportしようとしたのですがなぜか上手くいかず、結局npmでローカルインストールする方法を取りました。

構成

構成は以下のようになってます。

- /strapi/
  - Dockerfile
  - ...
- /next/
  - Dockerfile
  - ...
- /global-lib/
  - package.json
  - tsconfig.json
  - src/
  - dist/
- docker-compose.yml

各ファイルです。

/global-lib/package.json

{
  "name": "global-lib",
  "version": "1",
  "description": "shared between server and client",
  "license": "MIT",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "scripts": {
    "build": "tsc"
  }
}

/global-lib/tsconfig.json

{
  "compilerOptions": {
    "target": "es2016",
    "module": "commonjs",
    "rootDir": "./src/",
    "declaration": true,
    "outDir": "./dist/",
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}

/docker-compose.yml

Dockerの仕様でDockerfileより親を読み込めないので、contextとdockerfileを指定しています。これでdocker-compose(Dockerfileの親)から実行したことになり、その子にあたるglobal-libを取得できるようになります。

...
  strapi:
    container_name: strapi
    build:
      context: .
      dockerfile: ./strapi/Dockerfile
...

/strapi/Dockerfile

docker-composeから見た相対パスを指定してCOPYしています。

FROM node:18-alpine
# Installing libvips-dev for sharp Compatibility
RUN apk update && apk add --no-cache build-base gcc autoconf automake zlib-dev libpng-dev nasm bash vips-dev curl
ARG NODE_ENV=development
ENV NODE_ENV=${NODE_ENV}

# /opt/ に package.json と package-lock.json をコピー
WORKDIR /opt/strapi/
COPY ./strapi/package.json ./strapi/package-lock.json ./

# 依存関係のインストール
RUN npm config set fetch-retry-maxtimeout 600000 -g && npm ci

# PATH を設定
ENV PATH /opt/strapi/node_modules/.bin:$PATH

# アプリケーションコードをコピー。.dockerignore に記載されたファイルはコピーされない
COPY ./strapi/ /opt/strapi/
COPY ./global-lib/ /opt/global-lib/

# 所有権の変更
RUN chown -R node:node /opt/strapi
RUN chown -R node:node /opt/global-lib

RUN chmod +x /opt/strapi/entrypoint.sh
ENTRYPOINT ["/opt/strapi/entrypoint.sh"]

# ユーザーを node に変更
USER node

# ビルド
# RUN npm run build

# ポートを開放
EXPOSE 1337

# アプリケーションを実行
CMD ["npm", "run", "develop"]

/strapi/entrypoint.sh

entrypointでglobal-libをnpmインストールし直すことで、コンテナ再起動時に自動でglobal-libの変更が反映されます。なおこのファイルをホストにマウントしている場合は、実行できないこともあります。その場合は、ホスト側でも実行権限を付与してください。

2023/10/28追記:これは不要かもしれないです。どうやらローカルファイルをインストールした場合、特にupdate操作は必要ない可能性があります。

#!/bin/sh

npm uninstall global-lib
npm install ../global-lib

exec "$@"

省略

install

以下のように相対パスを指定すればnpmで読み込めます。

npm install ../global-lib

まとめ

以上でglobal-libをstrapiからimportして型定義を共通化できるようになりました。色々省略していますが、時間の関係上ご容赦ください。

ではでは。