以前に投稿したAngular+NestJS+OpenAPI(Swagger)でマイクロサービスを視野に入れた環境を考えるでNestJSとAngularを連携する部分がイマイチだなーと思ってたんですが、解決策が見つかったのでシンプルなモノリポ構成で作り直してみました。
前提
- Node.jsインストール済み
- Angular CLIインストール済み
- NestJSインストール済み
各種バージョン
$ node -v
v12.19.0
$ ng --version
_ _ ____ _ ___
/ \ _ __ __ _ _ _| | __ _ _ __ / ___| | |_ _|
/ △ \ | '_ \ / _` | | | | |/ _` | '__| | | | | | |
/ ___ \| | | | (_| | |_| | | (_| | | | |___| |___ | |
/_/ \_\_| |_|\__, |\__,_|_|\__,_|_| \____|_____|___|
|___/
Angular CLI: 10.2.0
Node: 12.19.0
OS: linux x64
Angular:
...
Ivy Workspace:
Package Version
------------------------------------------------------
@angular-devkit/architect 0.1002.0 (cli-only)
@angular-devkit/core 10.2.0 (cli-only)
@angular-devkit/schematics 10.2.0 (cli-only)
@schematics/angular 10.2.0 (cli-only)
@schematics/update 0.1002.0 (cli-only)
$ nest --version
7.5.1
環境構築
Lernaプロジェクト作成
Lernaはモノリポ管理用のツールです。
なくても問題はないですが、サーバーとクライアントのプロジェクトに対して一括でビルドやnpm installを実行することができるので便利です。
# プロジェクトのディレクトリを作成して移動
mkdir mono-nestjs-angular && cd mono-nestjs-angular
# Lernaプロジェクトとして初期化
npx lerna init
NestJSのテンプレート生成
# 各プロジェクトはlerna initで生成されるpackagesディレクトリの下に作成する
cd packages
nest new server
Angularのテンプレート生成
ng new client --style=scss --routing=true
API側のプレフィクスを設定する
APIコールと静的ファイルのアクセスを区別するため、APIは/apiから始まるURLとなるようにプレフィクスを設定します
やり方はコチラに書いてあります
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// APIのURLが/apiとなるようにプレフィクスを設定
app.setGlobalPrefix('api');
await app.listen(3000);
}
bootstrap();
Angularのページを静的ファイルとして返すように設定
今回やりたかったのはここですね。
リファレンスを参考に静的ファイルの設定を追加します
npx lerna add @nestjs/serve-static --scope=server
# もしくは
cd server && npm install --save @nestjs/serve-static
モジュールでServeStaticModuleをインポートします。この時のポイントは以下の通りです。
- rootPathはAngularの出力ディレクトリと一致するようにします
- Angularの出力ディレクトリはpackages/client/angular-cli.jsonのoutputPathに書いてあります
- excludeにて上記で設定したプレフィクスを指定してAPIコール時は参照しないようにします
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';
@Module({
imports: [
ServeStaticModule.forRoot({
rootPath: join(__dirname, '../../client/', 'dist/client'),
exclude: ['/api'],
}),
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
ビルド&起動スクリプト追加
プロジェクト直下のlerna initで生成されたpackage.jsonに起動コマンドを追加します
{
"name": "root",
"private": true,
"scripts": {
"start": "lerna run start --scope=server --stream",
"build": "lerna run build --stream"
},
"devDependencies": {
"lerna": "^3.16.1"
}
}
- lerna runコマンドはプロジェクト内のpackage.jsonにあるscriptsで定義されたコマンドを一括実行します
- startに関してはサーバーのみ実行するため、--scopeオプションでパッケージを指定する
- --streamオプションはログ出力するためのオプションです。これがないとサーバー起動してもログが出てきません。
動作確認
プロジェクトルートで以下のコマンドを実行します
起動
npm install
npm run build
npm start
画面確認
http://localhost:3000にアクセスします
ちゃんとAngularのページが出てますね。
API確認
http://localhost:3000/apiにもアクセスしてみます
APIもコールできていますね
補足:サブディレクトリにURL直打ちで遷移できるのか
SPAの場合、index.htmlからjsで画面を遷移しているように見せているだけで、URLでサブディレクトリを指定しても実際にそのサブディレクトリにindex.htmlがあるわけではないので、普通に静的ファイルを公開するだけだとURL直打ちやサブディレクトリでF5リロードすると404になってしまいます。
(index.htmlをコピーして404.htmlを作成して対応するのが一般的か?)
ここのページでも注意書きにありますが、@nestjs/serve-static
ではこの辺をうまいことやってくれるそうなので、試してみます。
ちなみに、以前書いたAngular+NestJS+OpenAPI(Swagger)でマイクロサービスを視野に入れた環境を考えるでは、app.use(regx, express.static(path))
のように必ずindex.htmlを返すようにしていました。
コンポーネントを追加する
Page1ComponentとPage2Componentコンポーネントを追加します
import { Component } from '@angular/core';
@Component({
selector: 'app-page1',
template: `
<h1>{{title}}</h1>
<a routerLink="/page2">to page2</a>
`
})
export class Page1Component {
title = 'page1';
}
※Page2Componentはpage1の部分をpage2に変えただけ
追加したコンポーネントを参照できるようにmoduleのdeclarationsに指定します
・・・
import { Page1Component } from './pages/page1.component';
import { Page2Component } from './pages/page2.component';
@NgModule({
declarations: [
AppComponent,
Page1Component,
Page2Component,
],
・・・
ルーティングの設定をします
・・・
import { Page1Component } from './pages/page1.component';
import { Page2Component } from './pages/page2.component';
const routes: Routes = [
{ path: 'page1', component: Page1Component },
{ path: 'page2', component: Page2Component },
];
・・・
画面を表示してみる
http://localhost:3000/page1にアクセスします
ちゃんと表示できました。
(下の方にPage1Componentの内容が出力されています)
一応、routerLinkd遷移できるかも確認するため、リンクを押下します
ちゃんとPage2Componentの内容に切り替わりました(URLも/page2になってますね)
まとめ
こんな感じで以前執筆した記事より簡単?にNestJSとAngularを連携させることが出来ました。
NestJSはAngularにインスパイアされただけあってSPAとの相性は良いですね^^
今回作ったものはGitHubにプッシュしてあります
https://github.com/teracy55/mono-nestjs-angular