blog.euxn.me

NestJS アプリケーションをプロダクションレディにする

2019-12-06 Fri.

この記事は NestJS アドベントカレンダー 2019 6 日目の記事です。

はじめに

この記事では、アプリケーションをプロダクションとして動かす上で必要な手順のうち、いくつかを紹介します。

サンプルコードのリポジトリは以下になります。

https://github.com/nestjs-jp/advent-calendar-2019/tree/master/day06-prepare-for-production-usage

なお、環境は執筆時点での Node.js の LTS である v12.13.1 を前提とします。

cli で雛形アプリケーションを作成

この記事では @nestjs/cli で生成される雛形に対して、プロダクションで実行するための設定を加えてゆきます。

1$ nest new day6-prepare-for-production-usage

config を作る

公式ドキュメントの Configuration の項では、環境変数を活用するのが良いと説明されています。 重厚にやる場合はドキュメントのように dotenv 等を使うのが良いですが、このサンプルでは小さいので、 NODE_ENV での分岐をベースにした config ファイルを作成します。

1import { LogLevel } from '@nestjs/common';
2
3interface Config {
4 logLevel: LogLevel[];
5}
6
7const develop: Config = {
8 logLevel: ['debug', 'log', 'verbose', 'warn', 'error'],
9};
10const production: Config = {
11 logLevel: ['log', 'verbose', 'warn', 'error'],
12};
13
14export const config =
15 process.env.NODE_ENV === 'produiction' ? production : develop;

アプリケーションの logger を設定する

NestFactory.create() の引数にオプションを渡すことで、 logger のログレベルを設定できます。先ほどの config を用いて設定してみます。 また、 app.useLogger を指定することで、 logger を指定することができます。 デフォルトで NestJS の提供する logger を使っているのですが、次で使用するので明示的に宣言しておきます。

main.ts
1async function bootstrap() {
2 const app = await NestFactory.create(AppModule, { logger: config.logLevel });
3 const logger = new Logger();
4 app.useLogger(logger);
5
6 await app.listen(3000);
7}

middleware にリクエストロガーを設定する

NestJS はデフォルトの場合は express のエンジンを使用するため、 express の作法で middleware を記述することができます。

request-logger.middleware.ts
1import {
2 Request as ExpressRequest,
3 Response as ExpressResponse,
4} from 'express';
5
6export function requestLogger(
7 logger: any,
8): (req: ExpressRequest, res: ExpressResponse, next: () => void) => void {
9 return (req, res, next): void => {
10 res.on('finish', (): void => {
11 logger.info(`${req.method} ${req.url} -> ${res.statusCode}`);
12 });
13 next();
14 };
15}

middleware の設定も express と同じように app.use() で設定することができます。

main.ts
1async function bootstrap() {
2 const app = await NestFactory.create(AppModule, { logger: config.logLevel });
3 const logger = new Logger();
4 app.useLogger(logger);
5 app.use(requestLogger(logger));
6
7 await app.listen(3000);
8}

CORS の設定

NestJS の標準設定では CORS は不許可なので、別のドメインからのアクセスを弾きます。 別ドメインにホスティングしたフロントエンドから NestJS アプリケーションの API を叩けるようにするためには、 CORS を有効にする設定が必要です。

試しに、 fetch を行うだけの html を作り、そこから Nest アプリケーションの API を叩いてみます。

public/index.html
1<!DOCTYPE html>
2<html lang="en">
3 <head>
4 <meta charset="UTF-8" />
5 <title>CORS sample</title>
6 <script>
7 fetch('http://localhost:3000')
8 .then(res => res.text())
9 .then(text => console.log(text));
10 </script>
11 </head>
12 <body></body>
13</html>

NestApplication での CORS を許可する設定は 2 種類あります。両方紹介します。

  1. NestFactory.create(){ cors: true } のオプションを渡す。
main.ts
1async function bootstrap() {
2 const app = await NestFactory.create(AppModule, { cors: true });
3 const logger = new Logger();
4 app.useLogger(logger);
5 app.use(requestLogger(logger));
6
7 await app.listen(3000);
8}
  1. app.enableCors() する。
main.ts
1async function bootstrap() {
2 const app = await NestFactory.create(AppModule);
3 const logger = new Logger();
4 app.useLogger(logger);
5 app.use(requestLogger(logger));
6 app.enableCors()
7
8 await app.listen(3000);
9}

それぞれ、オプションとして CORS の設定が渡せます。デフォルトでは全許可なので、必要に応じて絞り込んでください。

cors-oprions.interface.d.ts
1export interface CorsOptions {
2 /**
3 * Configures the `Access-Control-Allow-Origins` CORS header. See [here for more detail.](https://github.com/expressjs/cors#configuration-options)
4 */
5 origin?: boolean | string | RegExp | (string | RegExp)[] | CustomOrigin;
6 /**
7 * Configures the Access-Control-Allow-Methods CORS header.
8 */
9 methods?: string | string[];
10 /**
11 * Configures the Access-Control-Allow-Headers CORS header.
12 */
13 allowedHeaders?: string | string[];
14 /**
15 * Configures the Access-Control-Expose-Headers CORS header.
16 */
17 exposedHeaders?: string | string[];
18 /**
19 * Configures the Access-Control-Allow-Credentials CORS header.
20 */
21 credentials?: boolean;
22 /**
23 * Configures the Access-Control-Max-Age CORS header.
24 */
25 maxAge?: number;
26 /**
27 * Whether to pass the CORS preflight response to the next handler.
28 */
29 preflightContinue?: boolean;
30 /**
31 * Provides a status code to use for successful OPTIONS requests.
32 */
33 optionsSuccessStatus?: number;
34}

ビルドとプロダクション実行

@nestjs/clinest start コマンドは、内部で TypeScript をコンパイルしてから実行しているため、起動が遅くなっています。 開発時は nest start --watch を使用することで自動でビルド 〜 再起動までしてくれるため回避できますが、プロダクションでは、特にクラウドネイティブな環境では起動が遅いことがパフォーマンスのネックとなることが往々にしてあります。

本来の TypeScript のアプリケーションと同様にビルドして実行するために、 @nestjs/cli では、 nest build コマンドが用意されています。 標準では dist ディレクトリにファイルが吐き出されるため、その中にある main.js を実行します。

1$ yarn build
2$ node dist/main.js

終わりに

この記事では、アプリケーションをプロダクションとして動かす上で必要な手順のうち、いくつかを紹介しました。全てではありませんが、 express をベースとした手法は上記の方法でほとんど実現できると思われます。

明日は @potato4d さんによる、サンプルアプリケーションの実装です。