NestJS+Nuxt.jsでTypescriptオンリーなWebアプリ環境を作る

Nuxt.jsを使ってWebアプリ作ってみようかなーと思って、環境を作った時の手順を備忘録として残しておきます。

# 各種バージョン

```shell
$ node -v
v12.16.3
$ npm -v
6.14.4
$ nest -v
7.6.0
```

# 環境構築

フロントとバックエンドを別プロジェクトで作成して、モノリポで管理するためLernaでプロジェクトを作成します。

```shell
$ mkdir web-sample
$ cd web-sample
$ lerna init
lerna notice cli v4.0.0
lerna info Initializing Git repository
lerna info Creating package.json
lerna info Creating lerna.json
lerna info Creating packages directory
lerna success Initialized Lerna files
```

### フロント／バックエンドのプロジェクト作成

```shell
$ cd packages

# フロントエンド作成
$ npx create-nuxt-app front

create-nuxt-app v3.6.0
✨  Generating Nuxt.js project in front
? Project name: front
? Programming language: TypeScript
? Package manager: Npm
? UI framework: Element
? Nuxt.js modules: (Press <space> to select, <a> to toggle all, <i> to invert se
lection)
? Linting tools: (Press <space> to select, <a> to toggle all, <i> to invert sele
ction)
? Testing framework: None
? Rendering mode: Universal (SSR / SSG)
? Deployment target: Static (Static/Jamstack hosting)
? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert 
selection)
? What is your GitHub username? teracy55
? Version control system: Git


# バックエンド生成
$ nest new server
⚡  We will scaffold your app in a few seconds..

CREATE server/.eslintrc.js (631 bytes)
CREATE server/.prettierrc (51 bytes)
CREATE server/README.md (3339 bytes)
CREATE server/nest-cli.json (64 bytes)
CREATE server/package.json (1968 bytes)
CREATE server/tsconfig.build.json (97 bytes)
CREATE server/tsconfig.json (339 bytes)
CREATE server/src/app.controller.spec.ts (617 bytes)
CREATE server/src/app.controller.ts (274 bytes)
CREATE server/src/app.module.ts (249 bytes)
CREATE server/src/app.service.ts (142 bytes)
CREATE server/src/main.ts (208 bytes)
CREATE server/test/app.e2e-spec.ts (630 bytes)
CREATE server/test/jest-e2e.json (183 bytes)

? Which package manager would you ❤️  to use? npm
✔ Installation in progress... ☕

🚀  Successfully created project server
👉  Get started with the following commands:

$ cd server
$ npm run start

                                         
                          Thanks for installing Nest 🙏
                 Please consider donating to our open collective
                        to help us maintain this package.
                                         
                                         
               🍷  Donate: https://opencollective.com/nest
                                         

```

# サーバー側修正

ローカル環境ではフロント側もNuxt.jsのサーバー機能使って別サーバーとして起動させるため、CORSを許可します

```server/src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

const env = process.env.NODE_ENV || 'develop';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  if (env === 'develop') {
    app.enableCors();
  }
  await app.listen(3000);
}
bootstrap();

```

# フロント側修正

### Nuxt.jsの起動ポートを変える

Nuxt.jsのデフォルトポートが3000となっていて、NestJSのポートと被るためポートを変更します。
※本番ではビルドしたHTMLを静的ファイルとして公開するため関係ないです

```front/nuxt.config.js
  server: {
    port: 8000,
  },
```

### axiosの設定

API呼び出しのためにaxiosをインストールします
参考：https://axios.nuxtjs.org/setup

```shell
$ npm install @nuxtjs/axios
```

```front/nuxt.config.js
  modules: [
    '@nuxtjs/axios'
  ],
```

```front/tsconfig.json
    "types": [
      "@nuxt/types",
      "@nuxtjs/axios",
      "@types/node"
    ]
```

### コンポーネントの修正

```front/pages/index.vue
<template>
  <div class="container">
    <div>
      <Logo />
      <h1 class="title">
        front
      </h1>
      {{title}}
      ・・・
</template>
<script lang="ts">
import Vue from 'vue';

export default Vue.extend({
  data() {
    return {
      title: '',
    }
  },
  async mounted() {
    this.title = await this.getMessage();
  },

  methods: {
    getMessage(): Promise<string> {
      return this.$axios.$get('http://localhost:3000');
    }
  }
})
</script>
```

# 起動する

ターミナルを２つ開きます

- サーバー側を起動

```shell
cd packages/server
npm run start:dev
```

- フロント側を起動

```shell
cd packages/front
npm run dev
```

- `localhost:8000`にアクセスします

![スクリーンショット 2021-05-12 19.49.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/325108/8c3d853b-bbd8-1f40-7ece-db5a4af0151d.png)

ちゃんとサーバーから取得した`Hello World!`が表示されました！


# 本番向け

本番環境ではフロント/バックエンド共にトランスパイルしたJSで動かしたいので、手を加えます

### バックエンド側を修正

- フロント側の静的ファイルへのルーティングを設定
  - 参考：https://docs.nestjs.com/recipes/serve-static

```shell
$ npm install --save @nestjs/serve-static
```

- フロント側のビルドファイルは`packages/front/dist`に出力されるため、app.module.tsからの相対パスで指定します

```server/src/app.module.ts
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, '../../front/', 'dist'),
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
```

- APIのURLを`/api`ではじまるようにグローバルプレフィクスを設定します
  - 参考：https://docs.nestjs.com/faq/global-prefix

```server/src/main.ts
  const app = await NestFactory.create(AppModule);
  app.setGlobalPrefix('api');
```

- フロント側と本番ビルド用のスクリプト名を合わせます

```server/package.json
    "build:prod": "npm run build",
```

### フロント側を修正

- APIのURLを変更します

```front/pages/index.vue
  methods: {
    getMessage(): Promise<string> {
      return this.$axios.$get('http://localhost:3000/api');
    }
  }
```

- バックエンド側と本番ビルド用のスクリプト名を合わせます

```front/package.json
 "build:prod": "npm run generate",
```


### 本番ビルド

- lernaコマンド使って一括で本番ビルドします

```shell
$ lerna run build:prod
lerna notice cli v4.0.0
lerna info Executing command in 2 packages: "npm run build:prod"
lerna info run Ran npm script 'build:prod' in 'server' in 14.2s:

> server@0.0.1 build:prod /Users/terashimahiroshi/Desktop/dev/web-sample/packages/server
> npm run build


> server@0.0.1 prebuild /Users/terashimahiroshi/Desktop/dev/web-sample/packages/server
> rimraf dist


> server@0.0.1 build /Users/terashimahiroshi/Desktop/dev/web-sample/packages/server
> nest build

lerna info run Ran npm script 'build:prod' in 'front' in 27.9s:

> front@1.0.0 build:prod /Users/terashimahiroshi/Desktop/dev/web-sample/packages/front
> npm run generate


> front@1.0.0 generate /Users/terashimahiroshi/Desktop/dev/web-sample/packages/front
> nuxt generate

ℹ Doing webpack rebuild because nuxt.config.js modified
ℹ Production build
ℹ Bundling for server and client side
ℹ Target: static
ℹ Using components loader to optimize imports
ℹ Discovered Components: node_modules/.cache/nuxt/components/readme.md
✔ Builder initialized
✔ Nuxt files generated
ℹ Compiling Client
✔ Client: Compiled successfully in 16.57s
ℹ Compiling Server
✔ Server: Compiled successfully in 2.14s
ℹ Full static generation activated
ℹ Generating output directory: dist/
ℹ Generating pages with full static mode
✔ Generated route "/"
✔ Client-side fallback created: 200.html
✔ Static manifest generated
lerna success run Ran npm script 'build:prod' in 2 packages in 27.9s:
lerna success - front
lerna success - server
```

# サーバー起動

```shell
cd packages/server
npm run start:prod
```

- `localhost:3000`にアクセスするとフロント側の画面が表示されます
  - ※本番はNestJS側のポート（3000）なので注意