こんにちは、スタメンエンジニアの手嶋です。普段はRuby on RailsやReactなどの技術を用いて開発しています。最近はフィーチャーチーム体制に切り替わったこともあり、AWSなどの技術にも触れる機会が増えました。
これまで複数のプロジェクトにおいてReact(TypeScript)
で開発を行ってきました。そんな中でやはり型の恩恵を感じることが多かったのですが、バックエンドも含めてTypeScript
でアプリケーションを作成してみたいという想いが湧いてきたので、個人開発としてNext.js
とNestJS
で構築したアプリケーションをモノレポで運用し本番環境で動かしてみました。
モノレポはその名の通り、単一のリポジトリ(git等)で複数のプロジェクトを管理することです。 主に以下のようなメリットを享受できないかと思い、モノレポを採用しました。
- ESLint / Prettierなどの設定を一元管理できる
- frontend/backendを同時に変更する時に管理しやすくなる
本記事では Next.js/NestJSとモノレポ で実際に運用するまでに試した方法やリリースまでに苦戦した点を中心に紹介したいと思います。
※上記に焦点を当てているため、Next.js
やNestJS
等個々の技術に関する詳細説明は割愛しております。
目次
- 完成イメージ/技術スタック
- フロントエンドバックエンド共通
- フロントエンド
- バックエンド
- まとめ
完成イメージ
早速ですが、モノレポでの大まかな完成形イメージは以下のようなディレクトリ構成となります。ルート配下にfrontend
とbackend
というディレクトリを持ち、それぞれの中でNext.js
とNestJS
を作成しています。
// アプリケーション全体の構成 root─┬─ frontend #Next.js │ ├─ package.json #frontend固有のpackageを管理 │ └─ その他のファイル │ ├─ backend #NestJS │ ├─ package.json #backend固有のpackageを管理 │ └─ その他のファイル │ ├─ package.json #共有で扱うpackageを管理 └─ yarn.lock #全体で1つ
各ディレクトリ配下で個別にpackage
を管理するために、それぞれpackage.json
を作成していますが、ルートでもpackage.json
を管理しています(計3つ)。ルートのpackage.json
で管理するのはeslint
やprettier
など、アプリケーションで共通となるパッケージです。
※yarn.lockに関しては、全てのpackageの依存を管理した上で、アプリケーション内に1つだけ生成されるようです。
技術スタック
詳細に触れないものもありますが、以下のような技術を使用しています。
- Backend
- NestJS
- Prisma
- GraphQL / Apollo
- Frontend
- Next.js / React
- GraphQL Code Generator / Apollo Client
- その他
- yarn workspace
- ESLint / Prettier / husky
- Docker
- Vercel(Next.jsをホスティング)
- heroku(NestJSをホスティング)
フロントエンドバックエンド共通
最初にフロントエンドバックエンドで共通となる部分の紹介です。
上述のように同じアプリケーション内でフロントエンドとバックエンドをそれぞれを管理するために、Workspaces
というYarn
の機能を使用しています。(追加のpackageなどは特に必要ありません。)
こちらを使用することで、ひとつのリポジトリで複数のパッケージを開発できるようになり、効率的に作業を進められるようになります。
例えば、ルートのpackage.json
を以下のような記述にする事で、その配下のfrontend
とbackend
という複数のpackage.json
の管理をすることができます。
またルートから各ディレクトリを参照できるようになるので、backend
に追加のパッケージをインストールしたい場合はyarn backend add パッケージ名
というコマンドをルートで実行するだけでよく、各ディレクトリへ移動する必要がありません。
※注意点として、ここでpackage
というキーに指定する値はディレクトリの名前ではなく、各ディレクトリ内のpackage.json
のname属性の値と統一させる必要があります。
nohoist
に関しては必須ではないと思いますが、実際の運用にあたって必要だったため導入しています。詳細はバックエンドの章で後述します。
{ "name": "app-name", "private": true, "workspaces": { "packages": [ "frontend", "backend" ], "nohoist": [ "**/backend", "**/backend/**", "**/frontend", "**/frontend/**" ] }, "scripts": { "backend": "yarn workspace backend", "frontend": "yarn workspace frontend" }, // その他devDependenciesなどを記述する }
バックエンド
バックエンドはNestJSで構築し、ホスティング先には無料で使えるheroku
を選びました。クライアントとGraphQL
で通信するために、NestJs
側にもGraphQL / Apollo
をインストールしました。
モノレポで管理するにあたってポイントとなったのは以下2点です。
1つ目はdefalutブランチにマージした際にbackend
配下のみの変更を検知してheroku
へのデプロイを開始することです。(frontend
の変更のみであればデプロイをしない)
こちらに関してはgithub/actions
を使って実現しました。
プロジェクト内に以下のようなファイルを作成した上で、今回はheroku
へのデプロイなのでHEROKU_API_KEYのAPIをgithub
のsettingに設定しました。
name: Deploy backend on: push: branches: - main paths: - "backend/**" jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v1 - name: Release app backend uses: akhileshns/heroku-deploy@v3.0.4 with: heroku_api_key: ${{secrets.HEROKU_API_KEY}} heroku_app_name: "app_name" heroku_email: "hoge@gmail.com" env: HD_APP_BASE: "backend"
2つ目はルートのpackage.json
におけるnohoist
の設定です。
ORMとしてPrismaを導入したのですが.envに記載するDATABASE_URL
を参照できずPrisma
を起動できないという問題に直面しました。調査の結果、プロジェクト(ルート)レベルのnode_modules
にPrisma
がインストールされてしまうと正常に動作しないということが分かりました。
解決策として、Workspaces
のnohoist
の設定で各ディレクトリを指定することでプロジェクトレベルではなくパッケージレベル(ここではfrontend
とbackend
を指します)のnode_modules
にインストールすることでこの問題を解決しています。
※ディレクトリの名前ではなく、ディレクトリ内のpackage.json
のnameの値と統一する必要があります。
{ // その他の記述 "workspaces": { "packages": [ "frontend", "backend" ], "nohoist": [ "**/backend", "**/backend/**", "**/frontend", "**/frontend/**" ] }, // その他の記述 }
このようにWorkspaces
はその性質上、デフォルトではすべてのpackage
がルートのnode_modules
に入ってしまいますが、各ディレクトリレベルで管理したいpackage
はそのディレクトリで管理した方が問題が起きにくいと考えています。
フロントエンド
続いてフロントエンドです。フロントエンドはNext.js/React
で構築し、最もメジャーなVercel
にホスティングしました。バックエンドにGraphQL
でリクエストするためにApollo Client
を導入しています。またバックエンドを含めた型の統一管理や、型を記述するコストを削減するためにGraphQL Code Generator
を試してみました。
実際の運用にあたってのポイントになったのは以下2点です。
1つ目はVercel
のRoot Directory
設定の変更です。
上述のように各ディレクトリで、それぞれに必要なパッケージをインストールしています(Next.js
はfrontend
)が、Vercel
のデプロイ等の各設定はルートにNext.js
アプリケーションが構築されることを前提としている為、その部分の手当てをする必要がありました。
2つ目は余分なデプロイ処理を行わないようしたことです。 前章(バックエンド)の内容と重複する部分がありますが、基本的にデプロイは以下のように、各ディレクトリに対して差分があるPRがマージされた時のみ行いたいという考えました。
frontend
の差分のみ ->Vercel
へデプロイbackend
の差分のみ ->heroku
へデプロイ- 両方差分がある場合は2つのデプロイ処理を実行
この2つを実現する為にVercel
上で以下二箇所を変更しました。
1.Root Directoryの変更
Settingsタブ/Generalで表示されているRoot Directory
をNext.js
をインストールしているディレクトリに変更します。
今回はfrontend
という命名にしている為、そちらに合わせる形にしています。
2.Ignored Build Stepの設定
Vercel
ではIgnored Build Step
を設定することで、特定の条件においてcommit及びマージ時のBuild処理をスキップすることが可能です。こちらはSettingsタブ/Gitで設定可能で、ファイルの実行を指定することなどもできますが、今回は単純にディレクトリ指定としています。
まとめ
以上が、Next.js
×NestJS
をモノレポで実際に運用する方法と、リリースまでに苦戦した点です。実際には他にも色々な躓きポイントがありましたが、今回はTypeScript×モノレポ
という点に絞って紹介させていただきました。
Next.js
とNestJS
共にホスティング先は様々な選択肢があるため、完全に同じような構成にはならないと思いますが、TypeScript×モノレポ
でアプリケーションの構築及び運用を目指す方にとって少しでも参考になれば幸いです。
今回は個人開発で扱う技術スタックに関する紹介となりましたが、弊社の事業も拡大や多角化に伴い、開発組織が直面する課題や求められる能力/技術スタックが日々変化しています。 加えて、弊サービスのTUNAGでは、お客様である企業の成長にエンゲージメントという側面から貢献するために、まだまだ開発すべきことがたくさんあります。
エンジニアとして、やりがいのある仕事がしたい!わくわくする仕事をしたい!という方がいらっしゃいましたらぜひ、弊社採用窓口までご連絡ください。
TUNAGの開発体制、使用技術については下記ブログ記事を参照ください。 TUNAGの技術と開発体制のすべてを紹介します!
また株式会社スタメンでは、オンラインサロン用プラットホームFANTSというサービスも開発・運営しております。 ご興味がある方はぜひ下記のリンクをご覧ください。 FANTSの開発技術・開発組織を紹介します!