1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

超シンプルなTODOアプリを環境モリモリで構築してみた(Nuxt+OpenAPI+NestJS環境をdocker-composeで作る)

Posted at

最近Nuxt.jsにハマっていて、1つ自分用のベースとなる環境を作っておきたいなと思い、TODOアプリを環境モリモリで作成してみることにしてみました。

作ったモノ

GitHubに置いてあります

機能紹介

  • TODOアプリの機能としては一覧表示/新規追加/編集/完了⇄未完切り替え/削除だけのシンプルなものになります

  • その他、開発用にSwaggerUIやpgadminを使える様にしています

  • メイン画面
    image.png

  • 登録/編集ダイアログ
    image.png

  • 削除確認
    image.png

  • SwaggerUI(ローカル環境のみ)

    • API仕様確認およびAPIテスト用のつーる

image.png

  • Pgadmin(ローカル環境のみ)

image.png

環境

共通

Lernaでモノリポ管理

  • ルートプロジェクトをLernaで作成します
mkdir todo
cd todo
npx lerna init
  • スクリプトを追加
    • 後述しますが、.envおよびdocker-compose.{dev|prod}.ymlにて環境の切り分けを行います
    • シンプルに切り替えができる様に全てスクリプトに記載しておきます
      • 詳細はREADMEに記載してます
package.json
  "scripts": {
    "init:dev": "cp -a .env.local .env && npm install",
    "init:prod": "cp -a .env.prod .env && npm install",
    "postinstall": "lerna bootstrap",
    "build": "lerna run build:prod",
    "dev": "docker-compose --file=docker-compose.dev.yml up",
    "start": "docker-compose --file=docker-compose.prod.yml up"
  },

サーバー⇄フロント間のやりとりはOpenAPIでタイプセーフに行う

サーバー側の対応

フロント側の対応

  • ↓の投稿で記載した対応を行います

ローカル用ポイント

docker-compose.dev.yml

docker-compose.dev.yml
version: '3'
services:
  server:
    image: node:12-alpine
    container_name: todo-server-dev
    working_dir: /develop
    volumes:
      - ./packages/server:/develop
    depends_on:
      - db
    command: 'npm run start:dev'
    environment:
      - PORT=${SERVER_PORT}
      - NODE_ENV=${NODE_ENV}
      - DATABASE_HOST=db
      - DATABASE_PORT=5432
      - DATABASE_NAME=${DATABASE_NAME}
      - DATABASE_USER=${DATABASE_USER}
      - DATABASE_PASSWORD=${DATABASE_PASSWORD}

  front:
    image: node:12-alpine
    container_name: todo-front-dev
    working_dir: /develop
    volumes:
      - ./packages/front:/develop
    command: 'npm run dev'
    environment:
      - PORT=${FRONT_PORT}

  db:
    image: postgres:12-alpine
    container_name: todo-db-dev
    ports:
      - 5432:5432
    volumes:
      - .db/dev/data:/var/lib/postgresql/data
      - .db/dev/initdb:/docker-entrypoint-initdb.d
    environment:
      - POSTGRES_PASSWORD=${DATABASE_PASSWORD}
      - POSTGRES_USER=${DATABASE_USER}
      - POSTGRES_DB=${DATABASE_NAME}

  db-client:
    image: adminer
    container_name: db-client
    links:
      - db
    ports:
      - 8080:8080

  # フロントもNuxtサーバーでコンテナを立てているため、リバプロサーバーを立てて
  # localhostでフロント/サーバー両方にアクセスできるようにする
  proxy:
    image: nginx
    container_name: todo-proxy
    depends_on:
      - server
      - front
    volumes:
      - ./nginx/dev.conf.template:/etc/nginx/templates/default.conf.template
    ports:
      - 80:80
    environment:
      - SERVER_PORT=${SERVER_PORT}
      - FRONT_PORT=${FRONT_PORT}

  pgadmin:
    image: dpage/pgadmin4
    container_name: pgadmin
    ports:
      - 8000:80
    volumes:
      - ./.db/pgadmin4:/var/lib/pgadmin
    environment:
      - PGADMIN_DEFAULT_EMAIL=admin@example.com
      - PGADMIN_DEFAULT_PASSWORD=admin
    depends_on:
      - db

Nginx

  • フロント側に流す設定はNuxtのリファレンスに倣って記載しています
  • nginxにdocker-compose.ymlで指定した環境変数を指定する方法については以下で投稿してますので参考にしてください

nginx/dev.conf.template
# 参考 https://nuxtjs.org/docs/2.x/deployment/nginx-proxy
map $sent_http_content_type $expires {
    "text/html"                 epoch;
    "text/html; charset=utf-8"  epoch;
    default                     off;
}

server {
    listen 80;
    server_name localhost;

    gzip            on;
    gzip_types      text/plain application/xml text/css application/javascript;
    gzip_min_length 1000;

    location /api {
        proxy_pass http://server:${SERVER_PORT}/api;
        proxy_redirect off;
    }
    location / {
        # 参考 https://nuxtjs.org/docs/2.x/deployment/nginx-proxy
        expires $expires;

        proxy_redirect                      off;
        proxy_set_header Host               $host;
        proxy_set_header X-Real-IP          $remote_addr;
        proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto  $scheme;
        proxy_read_timeout          1m;
        proxy_connect_timeout       1m;
        proxy_pass http://front:${FRONT_PORT}/;
    }
}

フロント側の改修

  • コンテナの外からアクセスできる様にhost: '0.0.0.0'を設定
  • portは環境変数のPORTを参照する様にして可変にできる様にする(別になくても問題ない)
packages/front/nuxt.config.ts
  server: {
    host: '0.0.0.0', // デフォルト: localhost
    port: process.env.PORT || 3000,
  },

サーバー側の改修

  • ローカル環境の場合のみSwaggerUIを使える様にしておきます
packages/server/src/main.ts
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.setGlobalPrefix('api');
  app.useGlobalPipes(new ValidationPipe({}));
  if (env === 'develop') {
    // OpenAPIの設定
    const config = new DocumentBuilder().setTitle('TODO App API example').setVersion('1.0').build();
    const document = SwaggerModule.createDocument(app, config);
    SwaggerModule.setup('api', app, document);
  }
  await app.listen(process.env.PORT || 3000);
}
bootstrap();
  • ローカル環境で起動している場合、http://localhost/apiにアクセスするとSwaggerUIが使えます

本番用ポイント

docker-compose.yml

  • serverコンテナのvolumesにfront側のビルドディレクトリを連携する
docker-compose.prod.yml
version: '3'
services:
  server:
    image: node:12-alpine
    container_name: todo-server
    working_dir: /develop
    volumes:
      # 最低限のディレクトリのみ連携する
      - ./packages/server/package.json:/develop/package.json
      - ./packages/server/node_modules:/develop/node_modules
      - ./packages/server/dist:/develop/dist
      # フロント側のビルドディレクトリは名前がかぶるため、リネームして配置
      - ./packages/front/dist:/develop/public
    links:
      - db
    command: 'npm run start:prod'
    environment:
      - PORT=${SERVER_PORT}
      - NODE_ENV=${NODE_ENV}
      - DATABASE_HOST=db
      - DATABASE_PORT=5432
      - DATABASE_NAME=${DATABASE_NAME}
      - DATABASE_USER=${DATABASE_USER}
      - DATABASE_PASSWORD=${DATABASE_PASSWORD}

  db:
    image: postgres:12-alpine
    container_name: todo-db
    ports:
      - 5432:5432
    volumes:
      - .db/prod/data:/var/lib/postgresql/data
      - .db/prod/initdb:/docker-entrypoint-initdb.d
    environment:
      - POSTGRES_PASSWORD=${DATABASE_PASSWORD}
      - POSTGRES_USER=${DATABASE_USER}
      - POSTGRES_DB=${DATABASE_NAME}

  proxy:
    image: nginx
    container_name: proxy
    depends_on:
      - server
    volumes:
      - ./nginx/prod.conf.template:/etc/nginx/templates/default.conf.template
    ports:
      - 80:80
    environment:
      - SERVER_PORT=${SERVER_PORT}

Nginx

  • port:80へのアクセスをサーバー側のポートに流します
nginx/dev.conf.template
server {
    listen 80;
    server_name localhost;

    gzip            on;
    gzip_types      text/plain application/xml text/css application/javascript;
    gzip_min_length 1000;

    location / {
        proxy_redirect                      off;
        proxy_pass http://server:${SERVER_PORT};
    }
}

NestJS側でフロントのビルドファイルを静的ファイルとして公開できる様にする

公式リファレンスを参考に静的ファイル公開対応をします

npm install --save @nestjs/serve-static
  • docker-compose.prod.ymlでフロント側のビルドディレクトリをpublicディレクトリに配置しているため、そこにパスを指定する
packages/server/src/app.module.ts
@Module({
  imports: [
    ServeStaticModule.forRoot({
      rootPath: join(__dirname, '..', 'public'),
    }),

最後に

実際に開発をする場合はCIの設定だったり他にもやることはありますが、とりあえずこれだけの環境があればある程度のWebアプリ開発で困ることはないかなーと思います。

難点としてはそこそこスペックがないと、ローカル環境が重いですね。
それでもホストOSに色々ソフトをインストールしたりするのに比べれば環境構築が楽なので今後何か開発する際は使って行こうかと思います。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?