最近Nuxt.jsにハマっていて、1つ自分用のベースとなる環境を作っておきたいなと思い、TODOアプリを環境モリモリで作成してみることにしてみました。
作ったモノ
GitHubに置いてあります
機能紹介
-
TODOアプリの機能としては一覧表示/新規追加/編集/完了⇄未完切り替え/削除だけのシンプルなものになります
-
その他、開発用にSwaggerUIやpgadminを使える様にしています
-
SwaggerUI(ローカル環境のみ)
- API仕様確認およびAPIテスト用のつーる
- Pgadmin(ローカル環境のみ)
環境
共通
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でタイプセーフに行う
サーバー側の対応
- NestJS側を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に色々ソフトをインストールしたりするのに比べれば環境構築が楽なので今後何か開発する際は使って行こうかと思います。