現在、Angular+Express.jsのモノリスな環境で作業を行っているのですが、クライアントサイド/サーバーサイドともにコードが大きくなってきて、メンテナンスが大変になってきました。。。
先日、ng-japan2019に参加した際に、Angular+Nest.jsプロジェクトをLernaでモノリポ管理しているという話を聞き、これは良さそうだということで試してみることにしました。
※ng-japan2019の発表の様子はYouTubeで見ることができます。
NestJS + TypeORM + Angularで作るPure TypeScriptなアーキテクチャ by 白石 俊平
Express.js⇒Nest.jsへの載せ替えもしたいので、お試しがてらAngular+Nest.jsのモノリスプロジェクトをクライアント/サーバーでソースコードを分けつつ、モノリポで管理できるようにしたいと思います。
環境
VirtualBox + VagrantでゲストOSとしてCentOSのイメージを使っています
Lernaのインストール
公式のGetting Startedに従って、Lernaのインストールおよびテンプレートの作成を行います。
$ mkdir lerna-repo && cd $_
$ npx lerna init
いちいちnpxで実行するのはめんどくさいのでグローバルにLernaをインストールします。
npm install -g lerna
クライアント/サーバー用のパッケージを追加
まずはクライアントサイドのパッケージを追加します。
lerna create client
色々聞かれますが、とりあえず何も入力せずEnter連打します。
続いてサーバーサイドのパッケージを追加します。
lerna create server
こちらも同様にEnter連打します。
以下のようなディレクトリとファイルが作成されます。
Nest.jsプロジェクト作成
Nest.jsのCLIをインストールしていない場合はインストールします。
npm install -g @nestjs/cli
Nest.jsのテンプレートプロジェクトを生成し、Lernaパッケージを追加した場所にコピーします。
nest new server
mv server/* packages/server/
Angularプロジェクト作成
AngularのCLIをインストールしていない場合はインストールします。
npm install -g @angular/cli
ng new client
mv client/* packages/client/
動くようにする
今のままだと、単純にNest.jsとAngularのプロジェクトがそれぞれ追加されただけなので、動くように修正していきます。
ビルド先の設定
クライアント/サーバーともにdist
ディレクトリにビルドファイルの出力を行うため、親の方にdistディレクトリを作成し、シンボリックリンクを作成します。
※この設定(シンボリックリンク)がなくても後述の「Nest.js ⇔ Angular連携」で直接packages/client/distを参照するようにすればいけます。
mkdir dist
ln -s ../packages/client/dist/client dist/client
ln -s ../packages/server/dist dist/server
※lerna-repoディレクトリ直下で実行します
試しにビルドしてみます
lerna run build
⇒クライアント/サーバー両方のnpm run build
が実行され、lerna-repo/dist
内は以下のようになります。
起動設定
親のpackage.jsonに起動コマンドを追加します。
"scripts": {
"start": "node dist/server/main.js"
}
この状態でnpm start
を実行するとサーバーが起動します。
が、このままだと、ただNest.jsのAPIを呼ぶだけでAngularが動かないので連携させます。
Nest.js ⇔ Angular連携
サーバーサイドでAngularコードを参照できるようにします。
また、APIのURLは/api/~
となるようにプレフィクスを設定しておきます。
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
// 追加 start
import * as express from 'express';
import { join } from 'path';
// 追加 end
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 追加 start
app.setGlobalPrefix('api');
app.use(express.static(join(__dirname, '..', 'client')));
app.use('/*', express.static(join(__dirname, '..', 'client/index.html')));
// 追加 end
await app.listen(3000);
}
bootstrap();
Angularとの連携に必要な作業はこれだけです!
起動してみる
npm start
で起動し、ブラウザでアクセスします!
無事、Nest.jsにアクセスしてAngularの画面を表示することができました!!!
ちなみにhttp://192.168.33.10:3000/api
にアクセスすると、Nest.js側のAPIを呼び出せます。(Hello World!!が返ってくるだけ)
AngularからNest.jsのAPIを呼んでみる
せっかくなので、AngularからNest.jsのAPIを呼んでみます。
HttpClientを使うのでModuleをインポートします。
import { HttpClientModule } from '@angular/common/http';
@NgModule({
imports: [
・・・
HttpClientModule
],
})
export class AppModule { }
app.component.ts
のngOnInitでHttpClientを使ってAPIを呼び出してtitleにセットします。
import { Component, OnInit } from '@angular/core';
import { HttpClient } from '@angular/common/http';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit {
title = 'client';
constructor(private http: HttpClient) { }
async ngOnInit() {
const result = await this.http.get('/api').toPromise();
this.title = result['message'];
}
}
サーバーサイドはJSON形式でオブジェクトで返却するようにします。
@Get()
getHello() {
return { message: this.appService.getHello()};
}
再度ブラウザで確認
サーバーを再起動し、再度ブラウザで確認してみます。
lerna run build
npm start
ちゃんとタイトル部分が「Hello World!!」に変わってます
まとめ
これで、サーバーサイドとクライアントサイドのコード管理を分けつつ、モノリスな環境で動かすことが出来ました!
コード分割により、package.jsonがスッキリしたり、コードが見やすくなったり、と結構いい感じです
デバッグをどうするかとかも考えないといけないですが、それはおいおいやって行こうかと思います。
参考
Lerna
ng-japan2019
NestJS + TypeORM + Angularで作るPure TypeScriptなアーキテクチャ by 白石 俊平