Build applications on NestJS and Angular and run them in two versions: via PM2 and via Docker Compose
To run the built applications in PM2 mode, the frontend will be embedded in the backend in the form of static files.
To run in the "Docker Compose" mode, the backend will be built as a Docker image, and the built-in static of the frontend will be transmitted via Nginx.
The database is start via "Docker Compose".
1. We install all the necessary packages and regenerate Prisma clients
After installing the packages, the generated Prisma clients are deleted, so you need to run the generation again.
Commands
# Install all need dependencies
npm install --save @nestjs/serve-static dotenv wait-on
# After installing the packages, the generated Prisma clients are deleted, so you need to run their generation again
npm i && npm run generate
Console output
npm install --save @nestjs/serve-static dotenv wait-on
added 3 packages, and audited 2770 packages in 11s
331 packages are looking for funding
run `npm fund` for details
18 vulnerabilities (6 moderate, 12 high)
To address issues that do not require attention, run:
npm audit fix
To address all issues (including breaking changes), run:
npm audit fix --force
Run `npm audit` for details.
2. Adding a module for connecting static to the NestJS application
The NestJS-mod application has a special section for connecting this type of modules [Core modules] (https://nestjs-mod.com/docs/guides/info/module-types#core-modules ), but at this stage, to simplify understanding, such global things will be connected at the AppModule
level.
Updated file apps/server/src/app/app.module.ts
import { createNestModule, NestModuleCategory } from '@nestjs-mod/common';
import { PrismaModule } from '@nestjs-mod/prisma';
import { ServeStaticModule } from '@nestjs/serve-static';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { join } from 'path';
export const { AppModule } = createNestModule({
moduleName: 'AppModule',
moduleCategory: NestModuleCategory.feature,
imports: [
PrismaModule.forFeature({ featureModuleName: 'app' }),
ServeStaticModule.forRoot({
rootPath: join(__dirname, 'assets', 'client'),
}),
],
controllers: [AppController],
providers: [AppService],
});
3. Adding a new PM2 configuration file to run the built backend and frontend application
There will be only one application, since the static of the frontend application lies next to the built backend application.
Creating the file ecosystem-prod.config.json
{
"apps": [
{
"name": "nestjs-mod-fullstack",
"script": "node dist/apps/server/main.js",
"node_args": "-r dotenv/config"
}
]
}
4. Adding new scripts and updating existing ones
There are a lot of scripts, but they are all needed for various application launch modes.
Groups of scripts similar in scope are combined into a certain header _____group name_____
.
Updating the section with scripts in the package' file.json
{
"scripts": {
"_____pm2-full dev infra_____": "_____pm2-full dev infra_____",
"pm2-full:dev:start": "npm run generate && npm run docker-compose:start-prod:server && npm run db:create && npm run flyway:migrate && npm run pm2:dev:start",
"pm2-full:dev:stop": "npm run docker-compose:stop-prod:server && npm run pm2:dev:stop",
"_____dev infra_____": "_____dev infra_____",
"serve:dev": "./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=serve",
"serve:dev:server": "./node_modules/.bin/nx serve server --host=0.0.0.0",
"_____pm2 dev infra_____": "_____pm2 dev infra_____",
"pm2:dev:start": "./node_modules/.bin/pm2 start ./ecosystem.config.json && npm run wait-on -- --log http://localhost:3000/api/health --log http://localhost:4200",
"pm2:dev:stop": "./node_modules/.bin/pm2 delete all",
"_____pm2-full prod infra_____": "_____pm2-full prod infra_____",
"pm2-full:prod:start": "npm run generate && npm run build -- -c production && npm run copy-front-to-backend && npm run docker-compose:start-prod:server && npm run db:create && npm run flyway:migrate && npm run pm2:start",
"pm2-full:prod:stop": "npm run docker-compose:stop-prod:server && npm run pm2:stop",
"_____prod infra_____": "_____prod infra_____",
"start": "./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=start",
"build": "npm run generate && npm run tsc:lint && ./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=build --skip-nx-cache=true",
"start:prod:server": "./node_modules/.bin/nx start server",
"_____pm2 prod infra_____": "_____pm2 prod infra_____",
"pm2:start": "./node_modules/.bin/pm2 start ./ecosystem-prod.config.json && npm run wait-on -- --log http://localhost:3000/api/health --log http://localhost:3000",
"pm2:stop": "./node_modules/.bin/pm2 delete all",
"_____docker-compose-full prod infra_____": "_____docker-compose-full prod infra_____",
"docker-compose-full:prod:start": "npm run generate && npm run build -- -c production && npm run docker:build:server:latest && export COMPOSE_INTERACTIVE_NO_CLI=1 && docker-compose -f ./.docker/docker-compose-full.yml --env-file ./.docker/docker-compose-full.env --compatibility up -d",
"docker-compose-full:prod:stop": "export COMPOSE_INTERACTIVE_NO_CLI=1 && docker-compose -f ./.docker/docker-compose-full.yml --env-file ./.docker/docker-compose-full.env --compatibility down",
"docker-compose-full:prod:only-start": "export COMPOSE_INTERACTIVE_NO_CLI=1 && docker-compose -f ./.docker/docker-compose-full.yml --env-file ./.docker/docker-compose-full.env --compatibility up -d",
"docker-compose-full:prod:test:e2e": "export BASE_URL=http://localhost:8080 && npm run test:e2e",
"_____docs_____": "_____docs_____",
"docs:infrastructure": "export NESTJS_MODE=infrastructure && ./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source,client* --all -t=serve --parallel=1 -- --watch=false --inspect=false",
"_____docker-compose infra_____": "_____docker-compose infra_____",
"docker-compose:start:server": "export COMPOSE_INTERACTIVE_NO_CLI=1 && docker compose -f ./apps/server/docker-compose.yml --compatibility up -d",
"docker-compose:stop:server": "export COMPOSE_INTERACTIVE_NO_CLI=1 && docker compose -f ./apps/server/docker-compose.yml down",
"_____docker-compose prod-infra_____": "_____docker-compose prod-infra_____",
"docker-compose:start-prod:server": "export COMPOSE_INTERACTIVE_NO_CLI=1 && docker-compose -f ./apps/server/docker-compose-prod.yml --env-file ./apps/server/docker-compose-prod.env --compatibility up -d",
"docker-compose:stop-prod:server": "export COMPOSE_INTERACTIVE_NO_CLI=1 && docker-compose -f ./apps/server/docker-compose-prod.yml --env-file ./apps/server/docker-compose-prod.env down",
"_____docker_____": "_____docker_____",
"docker:build:server:latest": "docker build -t nestjs-mod-fullstack-server:latest -f ./.docker/server.Dockerfile . --progress=plain",
"_____tests_____": "_____tests_____",
"test": "./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=test --skip-nx-cache=true --passWithNoTests --output-style=stream-without-prefixes",
"test:e2e": "./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=e2e --skip-nx-cache=true --output-style=stream-without-prefixes",
"test:server": "./node_modules/.bin/nx test server --skip-nx-cache=true --passWithNoTests --output-style=stream-without-prefixes",
"_____lint_____": "_____lint_____",
"lint": "npm run tsc:lint && ./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=lint",
"lint:fix": "npm run tsc:lint && ./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=lint --fix",
"tsc:lint": "./node_modules/.bin/tsc --noEmit -p tsconfig.base.json",
"_____db_____": "_____db_____",
"db:create": "./node_modules/.bin/nx run-many -t=db-create",
"_____flyway_____": "_____flyway_____",
"flyway:create:server": "./node_modules/.bin/nx run server:flyway-create-migration",
"flyway:migrate:server": "./node_modules/.bin/nx run server:flyway-migrate",
"flyway:migrate": "./node_modules/.bin/nx run-many -t=flyway-migrate",
"_____prisma_____": "_____prisma_____",
"prisma:pull:server": "./node_modules/.bin/nx run server:prisma-pull",
"prisma:pull": "./node_modules/.bin/nx run-many -t=prisma-pull",
"prisma:generate": "./node_modules/.bin/nx run-many -t=prisma-generate",
"_____utils_____": "_____utils_____",
"copy-front-to-backend": "rm -rf dist/apps/server/assets/client && cp -r dist/apps/client/browser dist/apps/server/assets/client",
"generate": "./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=generate --skip-nx-cache=true && npm run make-ts-list && npm run lint:fix",
"tsc": "tsc",
"nx": "nx",
"dep-graph": "./node_modules/.bin/nx dep-graph",
"make-ts-list": "./node_modules/.bin/rucken make-ts-list",
"manual:prepare": "npm run generate && npm run docs:infrastructure && npm run test",
"update:nestjs-mod-versions": "npx -y npm-check-updates @nestjs-mod/* nestjs-mod -u",
"rucken": "rucken",
"wait-on": "./node_modules/.bin/wait-on --timeout=240000 --interval=1000 --window --verbose"
},
"scriptsComments": {
"pm2-full:dev:start": ["Launching infrastructure and all applications in watch mode via PM2"],
"pm2-full:dev:stop": ["Stopping infrastructure and all applications in watch mode via PM2"],
"pm2:dev:start": ["Launching all applications in watch mode via PM2"],
"pm2:dev:stop": ["Stopping all applications in watch mode via PM2"],
"pm2-full:prod:start": ["Launching the infrastructure and building all applications, followed by launching them via PM2"],
"pm2-full:prod:stop": ["Stopping infrastructure and all applications running through PM2"],
"test:e2e": ["Running E2E tests for all applications"],
"copy-dist-front-to-dist-backend": ["Copying a built frontend application to a built backend"],
"wait-on": ["Utility for checking and waiting for site availability"],
"docker-compose-full:prod:start": ["Build and launching Docker Compose infrastructure with a backend in the form of a Docker container and frontend statics transmitted via Nginx"],
"docker-compose-full:prod:stop": ["Stopping Docker Compose infrastructure and all applications"],
"docker-compose-full:prod:only-start": ["Launching Docker Compose infrastructure with a backend in the form of a Docker container and frontend statics transmitted via Nginx"],
"docker-compose-full:prod:test:e2e": ["Launching E2E tests on an application launched via Docker Compose"],
"docker:build:server:latest": ["Building a Docker backend image"]
}
}
Descriptions of new scripts
Script | Comment |
---|---|
pm2-full:dev:start | Launching the infrastructure and all applications in watch mode via PM2 |
pm2-full:dev:stop | Stopping infrastructure and all applications in watch mode via PM2 |
pm2:dev:start | Launch all applications in watch mode via PM2 |
pm2:dev:stop | Stopping all applications in watch mode via PM2 |
pm2-full:prod:start | Launching the infrastructure and building all applications, followed by launching them via PM2 |
pm2-full:prod:stop | Stopping the infrastructure and all applications running through PM2 |
test:e2e | Running E2E tests for all applications |
copy-dist-front-to-dist-backend | Copying a built frontend application to a built backend |
wait-on | A utility for checking and waiting for site availability |
docker-compose-full:prod:start | Build and launch of the "Docker Compose" infrastructure with a backend in the form of a Docker container and frontend statics transmitted via Nginx |
docker-compose-full:prod:stop | Stopping the "Docker Compose" infrastructure and all applications |
docker-compose-full:prod:only-start | Launching the "Docker Compose" infrastructure with a backend in the form of a Docker container and frontend statics transmitted via Nginx |
docker-compose-full:prod:test:e2e | Launching E2E tests on an application launched via "Docker Compose" |
docker:build:server:latest | Building a Docker backend image |
5. We run unit tests, then run the entire infrastructure with all applications in watch mode and run E2E tests
Commands
npm run test
npm run pm2-full:dev:start
npm run test:e2e
Console output
$ npm run test
> @nestjs-mod-fullstack/source@0.0.0 test
> ./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=test --skip-nx-cache=true --passWithNoTests --output-style=stream-without-prefixes
> nx run app-angular-rest-sdk:test --passWithNoTests
> nx run app-rest-sdk:test --passWithNoTests
> nx run client:test --passWithNoTests
NX Running target test for 4 projects
✔ nx run app-rest-sdk:test (2s)
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
NX Running target test for 4 projects
✔ nx run app-angular-rest-sdk:test (2s)
—————————————————————————————————————————————————————————————————————————————————————————— ————————————————————————————————————————————————————————————
NX Running target test for 4 projects
With additional flags:
--passWithNoTests=true
✔ nx run client:test (5s)
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
NX Running target test for 4 projects
✔ nx run server:test (4s)
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
NX Successfully ran target test for 4 projects (6s)
With additional flags:
--passWithNoTests=true
$ npm run pm2-full:dev:start
> @nestjs-mod-fullstack/source@0.0.0 pm2-full:dev:start
> npm run generate && npm run docker-compose:start-prod:server && npm run db:create && npm run flyway:migrate && npm run pm2:dev:start
> @nestjs-mod-fullstack/source@0.0.0 generate
> ./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=generate --skip-nx-cache=true && npm run make-ts-list && npm run lint:fix
✔ nx run server:generate (13s)
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
NX Successfully ran target generate for project server (13s)
NX Nx detected a flaky task
server:generate
Flaky tasks can disrupt your CI pipeline. Automatically retry them with Nx Cloud. Learn more at https://nx.dev/ci/features/flaky-tasks
> @nestjs-mod-fullstack/source@0.0.0 make-ts-list
> ./node_modules/.bin/rucken make-ts-list
> @nestjs-mod-fullstack/source@0.0.0 lint:fix
> npm run tsc:lint && ./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=lint --fix
> @nestjs-mod-fullstack/source@0.0.0 tsc:lint
> ./node_modules/.bin/tsc --noEmit -p tsconfig.base.json
✔ nx run app-angular-rest-sdk:lint [existing outputs match the cache, left as is]
✔ nx run client:lint [existing outputs match the cache, left as is]
✔ nx run server-e2e:lint [existing outputs match the cache, left as is]
✔ nx run server:lint (1s)
————————————————————————————— —————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
NX Successfully ran target lint for 4 projects (1s)
With additional flags:
--fix=true
Nx read the output from the cache instead of running the command for 3 out of 4 tasks.
> @nestjs-mod-fullstack/source@0.0.0 docker-compose:start-prod:server
> export COMPOSE_INTERACTIVE_NO_CLI=1 && docker-compose -f ./apps/server/docker-compose-prod.yml --env-file ./apps/server/docker-compose-prod.env --compatibility up -d
server-postgre-sql is up-to-date
> @nestjs-mod-fullstack/source@0.0.0 db:create
> ./node_modules/.bin/nx run-many -t=db-create
✔ nx run server:db-create (746ms)
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
NX Successfully ran target db-create for project server (775ms)
> @nestjs-mod-fullstack/source@0.0.0 flyway:migrate
> ./node_modules/.bin/nx run-many -t=flyway-migrate
✔ nx run server:flyway-migrate (1s)
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
NX Successfully ran target flyway-migrate for project server (2s)
> @nestjs-mod-fullstack/source@0.0.0 pm2:dev:start
> ./node_modules/.bin/pm2 start ./ecosystem.config.json && npm run wait-on -- --log http://localhost:3000/api/health --log http://localhost:4200
[PM2][WARN] Applications server, client not running, starting...
[PM2] App [server] launched (1 instances)
[PM2] App [client] launched (1 instances)
┌────┬───────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name │ namespace │ version │ mode │ pid │ uptime │ ↺ │ status │ cpu │ mem │ user │ watching │
├────┼───────────┼─────────────┼─────────┼─────────┼──────────┼────────┼──────┼───────────┼──────────┼──────────┼──────────┼──────── ──┤
│ 1 │ client │ default │ N/A │ fork │ 175791 │ 0s │ 0 │ online │ 0% │ 13.1mb │ endy │ disabled │
│ 0 │ server │ default │ N/A │ fork │ 175790 │ 0s │ 0 │ online │ 0% │ 18.7mb │ endy │ disabled │
└────┴───────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
> @nestjs-mod-fullstack/source@0.0.0 wait-on
> ./node_modules/.bin/wait-on --timeout=240000 --interval=1000 --window --verbose --log http://localhost:3000/api/health --log http://localhost:4200
waiting for 2 resources: http://localhost:3000/api/health, http://localhost:4200
making HTTP(S) head request to url:http://localhost:3000/api/health ...
making HTTP(S) head request to url:http://localhost:4200 ...
HTTP(S) error for http://localhost:3000/api/health Error: connect ECONNREFUSED 127.0.0.1:3000
HTTP(S) error for http://localhost:4200 Error: connect ECONNREFUSED 127.0.0.1:4200
making HTTP(S) head request to url:http://localhost:3000/api/health ...
making HTTP(S) head request to url:http://localhost:4200 ...
HTTP(S) error for http://localhost:3000/api/health Error: connect ECONNREFUSED 127.0.0.1:3000
HTTP(S) error for http://localhost:4200 Error: connect ECONNREFUSED 127.0.0.1:4200
making HTTP(S) head request to url:http://localhost:3000/api/health ...
making HTTP(S) head request to url:http://localhost:4200 ...
HTTP(S) error for http://localhost:3000/api/health Error: connect ECONNREFUSED 127.0.0.1:3000
making HTTP(S) head request to url:http://localhost:3000/api/health ...
HTTP(S) error for http://localhost:3000/api/health Error: connect ECONNREFUSED 127.0.0.1:3000
making HTTP(S) head request to url:http://localhost:4200 ...
making HTTP(S) head request to url:http://localhost:3000/api/health ...
HTTP(S) error for http://localhost:3000/api/health Error: connect ECONNREFUSED 127.0.0.1:3000
making HTTP(S) head request to url:http://localhost:4200 ...
making HTTP(S) head request to url:http://localhost:3000/api/health ...
making HTTP(S) head request to url:http://localhost:4200 ...
HTTP(S) result for http://localhost:3000/api/health: {
status: 200,
statusText: 'OK',
headers: Object [AxiosHeaders] {
'x-powered-by': 'Express',
vary: 'Origin',
'access-control-allow-credentials': 'true',
'x-request-id': '72cc7a93-98b5-4e60-8c4e-65e9458385bf',
'cache-control': 'no-cache, no-store, must-revalidate',
'content-type': 'application/json; charset=utf-8',
'content-length': '107',
etag: 'W/"6b-ouXVoNOXyOxnMfI7caewF8/p97A"',
date: 'Sat, 17 Aug 2024 04:02:41 GMT',
connection: 'keep-alive',
'keep-alive': 'timeout=5'
},
data: ''
}
waiting for 1 resources: http://localhost:4200
making HTTP(S) head request to url:http://localhost:4200 ...
HTTP(S) result for http://localhost:4200: {
status: 200,
statusText: 'OK',
headers: Object [AxiosHeaders] {
'x-powered-by': 'Express',
'access-control-allow-origin': '*',
'content-type': 'text/html; charset=utf-8',
'accept-ranges': 'bytes',
'content-length': '586',
date: 'Sat, 17 Aug 2024 04:02:42 GMT',
connection: 'keep-alive',
'keep-alive': 'timeout=5'
},
data: ''
}
wait-on(175826) complete
$ npm run test:e2e
> @nestjs-mod-fullstack/source@0.0.0 test:e2e
> ./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=e2e --skip-nx-cache=true --output-style=stream-without-prefixes
> nx run client-e2e:e2e
> playwright test
NX Running target e2e for 2 projects and 1 task they depend on
NX Running target e2e for 2 projects and 1 task they depend on
→ Executing 1/3 remaining tasks...
⠧ nx run client-e2e:e2e
✔ nx run client-e2e:e2e (7s)
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
✔ nx run server:build:production (3s)