Connecting PrismaORM to a NestJS-mod application and testing its operation via REST

Previous article: Adding a Postgres database to a project and running migrations via Flyway for a NestJS-mod application

Prisma is connected to NestJS via a package https://www.npmjs.com/package/@nestjs-mod/prisma.

The work is checked by running E2E tests on REST endpoints.

1. Install all necessary packages

Teams

# Install all need dependencies
npm i --save @prisma/client @nestjs-mod/prisma

# Install all need dev-dependencies
npm i --save-dev prisma
Console output
$ npm i --save @prisma/client @nestjs-mod/prisma

added 2 packages, and audited 2501 packages in 27s

300 packages are looking for funding
  run `npm fund` for details

45 vulnerabilities (6 moderate, 39 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.

$ npm i --save-dev prisma

added 6 packages, and audited 2507 packages in 8s

300 packages are looking for funding
  run `npm fund` for details

45 vulnerabilities (6 moderate, 39 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. Add the PrismaModule module to the server code

Updated file apps/server/src/main.ts

import { DefaultNestApplicationInitializer, DefaultNestApplicationListener, InfrastructureMarkdownReportGenerator, PACKAGE_JSON_FILE, ProjectUtils, bootstrapNestApplication, isInfrastructureMode } from '@nestjs-mod/common';
import { DOCKER_COMPOSE_FILE, DockerCompose, DockerComposePostgreSQL } from '@nestjs-mod/docker-compose';
import { FLYWAY_JS_CONFIG_FILE, Flyway } from '@nestjs-mod/flyway';
import { NestjsPinoLoggerModule } from '@nestjs-mod/pino';
import { ECOSYSTEM_CONFIG_FILE, Pm2 } from '@nestjs-mod/pm2';
import { TerminusHealthCheckModule } from '@nestjs-mod/terminus';
import { MemoryHealthIndicator } from '@nestjs/terminus';
import { join } from 'path';
import { AppModule } from './app/app.module';
import { FakePrismaClient, PRISMA_SCHEMA_FILE, PrismaModule } from '@nestjs-mod/prisma';

const appFeatureName="app";
const rootFolder = join(__dirname, '..', '..', '..');
const appFolder = join(rootFolder, 'apps', 'server');

bootstrapNestApplication({
  modules: {
    system: [
      ProjectUtils.forRoot({
        staticConfiguration: {
          applicationPackageJsonFile: join(appFolder, PACKAGE_JSON_FILE),
          packageJsonFile: join(rootFolder, PACKAGE_JSON_FILE),
          envFile: join(rootFolder, '.env'),
        },
      }),
      DefaultNestApplicationInitializer.forRoot({
        staticConfiguration: { bufferLogs: true },
      }),
      NestjsPinoLoggerModule.forRoot(),
      TerminusHealthCheckModule.forRootAsync({
        configurationFactory: (memoryHealthIndicator: MemoryHealthIndicator) => ({
          standardHealthIndicators: [
            {
              name: 'memory_heap',
              check: () => memoryHealthIndicator.checkHeap('memory_heap', 150 * 1024 * 1024),
            },
          ],
        }),
        inject: [MemoryHealthIndicator],
      }),
      DefaultNestApplicationListener.forRoot({
        staticConfiguration: {
          // When running in infrastructure mode, the backend server does not start.
          mode: isInfrastructureMode() ? 'silent' : 'listen',
        },
      }),
    ],
    core: [
      PrismaModule.forRoot({
        staticConfiguration: {
          schemaFile: join(appFolder, 'src', 'prisma', `${appFeatureName}-${PRISMA_SCHEMA_FILE}`),
          featureName: appFeatureName,
          prismaModule: isInfrastructureMode()
            ? { PrismaClient: FakePrismaClient }
            : // remove after first run docs:infrastructure
              { PrismaClient: FakePrismaClient },
          // uncomment after first run docs:infrastructure
          // import(`@prisma/prisma-user-client`),
          addMigrationScripts: false,
        },
      }),
    ],
    feature: [AppModule.forRoot()],
    infrastructure: [
      InfrastructureMarkdownReportGenerator.forRoot({
        staticConfiguration: {
          markdownFile: join(appFolder, 'INFRASTRUCTURE.MD'),
          skipEmptySettings: true,
        },
      }),
      Pm2.forRoot({
        configuration: {
          ecosystemConfigFile: join(rootFolder, ECOSYSTEM_CONFIG_FILE),
          applicationScriptFile: join('dist/apps/server/main.js'),
        },
      }),
      DockerCompose.forRoot({
        configuration: {
          dockerComposeFileVersion: '3',
          dockerComposeFile: join(appFolder, DOCKER_COMPOSE_FILE),
        },
      }),
      DockerComposePostgreSQL.forRoot(),
      DockerComposePostgreSQL.forFeature({
        featureModuleName: appFeatureName,
      }),
      Flyway.forRoot({
        staticConfiguration: {
          featureName: appFeatureName,
          migrationsFolder: join(appFolder, 'src', 'migrations'),
          configFile: join(rootFolder, FLYWAY_JS_CONFIG_FILE),
        },
      }),
    ],
  },
});

3. We create documentation for the project and, in parallel, create additional code and scripts for working with PrismaORM

Teams

# Build all applications and library
npm run build

# Generate markdown report
npm run docs:infrastructure
Console output
$ npm run build

> @nestjs-mod-fullstack/source@0.0.0 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


> @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   Successfully ran target generate for 0 projects (34ms)


> @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 server-e2e:lint (2s)
   ✔  nx run server:lint (3s)
   ✔  nx run client:lint (3s)

——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

 NX   Successfully ran target lint for 3 projects (3s)

      With additional flags:
        --fix=true


> @nestjs-mod-fullstack/source@0.0.0 tsc:lint
> ./node_modules/.bin/tsc --noEmit -p tsconfig.base.json


   ✔  nx run server:build:production (4s)
   ✔  nx run client:build:production (9s)

——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

 NX   Successfully ran target build for 2 projects (9s)

 $ npm run docs:infrastructure

> @nestjs-mod-fullstack/source@0.0.0 docs:infrastructure
> export NESTJS_MODE=infrastructure && ./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=start --parallel=1


 NX   Running target start for project server:

- server

——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

> nx run server:start

> node dist/apps/server/main.js

[15:57:26.274] INFO (24548): Starting Nest application...
    context: "NestFactory"
[15:57:26.274] INFO (24548): DefaultNestApp dependencies initialized
    context: "InstanceLoader"
[15:57:26.274] INFO (24548): ProjectUtilsSettings dependencies initialized
    context: "InstanceLoader"
[15:57:26.274] INFO (24548): DefaultNestApplicationInitializerSettings dependencies initialized
    context: "InstanceLoader"
[15:57:26.274] INFO (24548): DefaultNestApplicationInitializerShared dependencies initialized
    context: "InstanceLoader"
[15:57:26.274] INFO (24548): NestjsPinoLoggerModuleSettings dependencies initialized
    context: "InstanceLoader"
[15:57:26.274] INFO (24548): NestjsPinoLoggerModuleShared dependencies initialized
    context: "InstanceLoader"
[15:57:26.274] INFO (24548): TerminusHealthCheckModuleSettings dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): DefaultNestApplicationListenerSettings dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): DefaultNestApplicationListenerShared dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): PrismaModuleSettings dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): AppModuleSettings dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): AppModuleShared dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): InfrastructureMarkdownReportGeneratorSettings dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): Pm2Settings dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): Pm2Shared dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): ProjectUtils dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): DockerComposeSettings dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): ProjectUtils dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): DockerComposePostgreSQLSettings dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): DockerCompose dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): DockerComposePostgreSQL dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): DockerComposePostgreSQLSettings dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): DockerComposePostgreSQLShared dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): FlywaySettings dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): FlywayShared dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): PrismaModuleSettings dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): PrismaModuleShared dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): ProjectUtils dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): InfrastructureMarkdownReportGeneratorSettings dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): ProjectUtils dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): InfrastructureMarkdownReportStorage dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): InfrastructureMarkdownReportStorageSettings dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): ProjectUtils dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): DockerCompose dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): FlywaySettings dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): FlywayShared dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): ProjectUtils dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): DefaultNestApplicationListenerSettings dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): DefaultNestApplicationListenerShared dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): DockerComposeShared dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): InfrastructureMarkdownReportStorageShared dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): AppModule dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): ProjectUtils dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): DefaultNestApplicationInitializer dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): DefaultNestApplicationListener dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): PrismaModule dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): InfrastructureMarkdownReportGenerator dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): DockerComposePostgreSQL dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): Flyway dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): DefaultNestApplicationListener dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): NestjsPinoLoggerModule dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): TerminusModule dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): TerminusModule dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): ProjectUtilsShared dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): InfrastructureMarkdownReportGeneratorShared dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): Pm2 dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): DockerCompose dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): DockerComposePostgreSQL dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): PrismaModule dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): InfrastructureMarkdownReportGeneratorShared dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): Flyway dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): InfrastructureMarkdownReportGenerator dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): LoggerModule dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): DockerComposePostgreSQLShared dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): PrismaModuleShared dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): TerminusHealthCheckModuleShared dependencies initialized
    context: "InstanceLoader"
[15:57:26.275] INFO (24548): TerminusHealthCheckModule dependencies initialized
    context: "InstanceLoader"
[15:57:26.282] INFO (24548): TerminusHealthCheckController {/health}:
    context: "RoutesResolver"
[15:57:26.284] INFO (24548): Mapped {/health, GET} route
    context: "RouterExplorer"
[15:57:26.284] INFO (24548): AppController {/}:
    context: "RoutesResolver"
[15:57:26.284] INFO (24548): Mapped {/, GET} route
    context: "RouterExplorer"
[15:57:26.286] INFO (24548): Connected to database!
    context: "PrismaClient"
[15:57:26.305] DEBUG (24548):
    0: "SERVER_ROOT_DATABASE_URL: Description='Connection string for PostgreSQL with root credentials (example: postgres://postgres:postgres_password@localhost:5432/postgres?schema=public, username must be \"postgres\")', Original Name="rootDatabaseUrl""
    1: "SERVER_PORT: Description='The port on which to run the server.', Default="3000", Original Name="port""
    2: "SERVER_HOSTNAME: Description='Hostname on which to listen for incoming packets.', Original Name="hostname""
    3: "SERVER_APP_DATABASE_URL: Description='Connection string for PostgreSQL with module credentials (example: postgres://feat:feat_password@localhost:5432/feat?schema=public)', Original Name="databaseUrl""
    context: "All application environments"
[15:57:26.331] INFO (24548): Nest application successfully started
    context: "NestApplication"

——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

 NX   Successfully ran target start for project server

4. After the first generation of the infrastructure code, it is necessary to modify the main application file

Updated file apps/server/src/main.ts

import { DefaultNestApplicationInitializer, DefaultNestApplicationListener, InfrastructureMarkdownReportGenerator, PACKAGE_JSON_FILE, ProjectUtils, bootstrapNestApplication, isInfrastructureMode } from '@nestjs-mod/common';
import { DOCKER_COMPOSE_FILE, DockerCompose, DockerComposePostgreSQL } from '@nestjs-mod/docker-compose';
import { FLYWAY_JS_CONFIG_FILE, Flyway } from '@nestjs-mod/flyway';
import { NestjsPinoLoggerModule } from '@nestjs-mod/pino';
import { ECOSYSTEM_CONFIG_FILE, Pm2 } from '@nestjs-mod/pm2';
import { TerminusHealthCheckModule } from '@nestjs-mod/terminus';
import { MemoryHealthIndicator } from '@nestjs/terminus';
import { join } from 'path';
import { AppModule } from './app/app.module';
import { FakePrismaClient, PRISMA_SCHEMA_FILE, PrismaModule } from '@nestjs-mod/prisma';

const appFeatureName="app";
const rootFolder = join(__dirname, '..', '..', '..');
const appFolder = join(rootFolder, 'apps', 'server');

bootstrapNestApplication({
  modules: {
    system: [
      ProjectUtils.forRoot({
        staticConfiguration: {
          applicationPackageJsonFile: join(appFolder, PACKAGE_JSON_FILE),
          packageJsonFile: join(rootFolder, PACKAGE_JSON_FILE),
          envFile: join(rootFolder, '.env'),
        },
      }),
      DefaultNestApplicationInitializer.forRoot({
        staticConfiguration: { bufferLogs: true },
      }),
      NestjsPinoLoggerModule.forRoot(),
      TerminusHealthCheckModule.forRootAsync({
        configurationFactory: (memoryHealthIndicator: MemoryHealthIndicator) => ({
          standardHealthIndicators: [
            {
              name: 'memory_heap',
              check: () => memoryHealthIndicator.checkHeap('memory_heap', 150 * 1024 * 1024),
            },
          ],
        }),
        inject: [MemoryHealthIndicator],
      }),
      DefaultNestApplicationListener.forRoot({
        staticConfiguration: {
          // When running in infrastructure mode, the backend server does not start.
          mode: isInfrastructureMode() ? 'silent' : 'listen',
        },
      }),
    ],
    core: [
      PrismaModule.forRoot({
        staticConfiguration: {
          schemaFile: join(appFolder, 'src', 'prisma', `${appFeatureName}-${PRISMA_SCHEMA_FILE}`),
          featureName: appFeatureName,
          prismaModule: isInfrastructureMode() ? { PrismaClient: FakePrismaClient } : import(`@prisma/app-client`), // <-- updates
          addMigrationScripts: false,
        },
      }),
    ],
    feature: [AppModule.forRoot()],
    infrastructure: [
      InfrastructureMarkdownReportGenerator.forRoot({
        staticConfiguration: {
          markdownFile: join(appFolder, 'INFRASTRUCTURE.MD'),
          skipEmptySettings: true,
        },
      }),
      Pm2.forRoot({
        configuration: {
          ecosystemConfigFile: join(rootFolder, ECOSYSTEM_CONFIG_FILE),
          applicationScriptFile: join('dist/apps/server/main.js'),
        },
      }),
      DockerCompose.forRoot({
        configuration: {
          dockerComposeFileVersion: '3',
          dockerComposeFile: join(appFolder, DOCKER_COMPOSE_FILE),
        },
      }),
      DockerComposePostgreSQL.forRoot(),
      DockerComposePostgreSQL.forFeature({
        featureModuleName: appFeatureName,
      }),
      Flyway.forRoot({
        staticConfiguration: {
          featureName: appFeatureName,
          migrationsFolder: join(appFolder, 'src', 'migrations'),
          configFile: join(rootFolder, FLYWAY_JS_CONFIG_FILE),
        },
      }),
    ],
  },
});

5. Launch the database, generate a PrismaORM schema from the existing database, and generate a PrismaORM client

Teams

# Start database
npm run docker-compose:start-prod:server

# Pull database-schema to prisma-schema (options --force rewirite exists prisma-schema)
npm run prisma:pull --force

# Generate prisma-client
npm run generate
Console output
$ npm run docker-compose:start-prod:server

> @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

Creating network "server_server-network" with driver "bridge"
Creating server-postgre-sql ... done

$ npm run prisma:pull --force
npm WARN using --force Recommended protections disabled.

> @nestjs-mod-fullstack/source@0.0.0 prisma:pull
> ./node_modules/.bin/nx run-many -t=prisma-pull


   ✔  nx run server:prisma-pull (583ms)

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

 NX   Successfully ran target prisma-pull for project server (612ms)


 NX   Nx detected a flaky task

  server:prisma-pull

Flaky tasks can disrupt your CI pipeline. Automatically retry them with Nx Cloud. Learn more at https://nx.dev/ci/features/flaky-tasks

$ npm run generate

> @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 (1s)

————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

 NX   Successfully ran target generate for project server (1s)


> @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 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 3 projects (1s)

      With additional flags:
        --fix=true

Nx read the output from the cache instead of running the command for 2 out of 3 tasks.

6. Open the created one and make sure that the PrismaORM schema completely repeats the application database schema.

Updated file apps/server/src/prisma/app-schema.prisma

generator client {
  provider   = "prisma-client-js"
  output     = "../../../../node_modules/@prisma/app-client"
  engineType = "binary"
}

datasource db {
  provider = "postgres"
  url      = env("SERVER_APP_DATABASE_URL")
}

model AppDemo {
  id        String   @id(map: "PK_APP_DEMO") @default(dbgenerated("uuid_generate_v4()")) @db.Uuid
  name      String   @unique(map: "UQ_APP_DEMO") @db.VarChar(128)
  createdAt DateTime @default(now()) @db.Timestamp(6)
  updatedAt DateTime @default(now()) @db.Timestamp(6)
}

model migrations {
  installed_rank Int      @id(map: "__migrations_pk")
  version        String?  @db.VarChar(50)
  description    String   @db.VarChar(200)
  type           String   @db.VarChar(20)
  script         String   @db.VarChar(1000)
  checksum       Int?
  installed_by   String   @db.VarChar(100)
  installed_on   DateTime @default(now()) @db.Timestamp(6)
  execution_time Int
  success        Boolean

  @@index([success], map: "__migrations_s_idx")
  @@map("__migrations")
}

7. Since the default pm2 configuration contains the launch of an already assembled application on NestJS, we change the command to launch the application in watch mode

Updated file ecosystem.config.json

{
  "apps": [
    {
      "name": "server",
      "script": "./node_modules/.bin/nx serve server",
      "node_args": "-r dotenv/config"
    },
    {
      "name": "client",
      "script": "./node_modules/.bin/nx serve client --host=0.0.0.0 --disable-host-check",
      "node_args": "-r dotenv/config"
    }
  ]
}

8. Restart all applications

Teams

# Stop all applications
npm run pm2:stop

# Start all applications
npm run pm2:start
Console output
$ npm run pm2:stop

> @nestjs-mod-fullstack/source@0.0.0 pm2:stop
> ./node_modules/.bin/pm2 delete all


>>>> In-memory PM2 is out-of-date, do:
>>>> $ pm2 update
In memory PM2 version: 3.1.3
Local PM2 version: 5.4.2

[PM2] Applying action deleteProcessId on app [all](ids: [ 0, 1 ])
[PM2] [client](1) ✓
[PM2] [server](0) ✓
┌────┬───────────┬─────────────┬─────────┬─────────┬──────────┬────────┬──────┬───────────┬──────────┬──────────┬──────────┬──────────┐
│ id │ name      │ namespace   │ version │ mode    │ pid      │ uptime │ ↺    │ status    │ cpu      │ mem      │ user     │ watching │
└────┴───────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘

$ npm run pm2:start

> @nestjs-mod-fullstack/source@0.0.0 pm2:start
> ./node_modules/.bin/pm2 start ./ecosystem.config.json


>>>> In-memory PM2 is out-of-date, do:
>>>> $ pm2 update
In memory PM2 version: 3.1.3
Local PM2 version: 5.4.2

[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    │ 53570    │ 0s     │ 0    │ online    │ 0%       │ 16.2mb   │ endy     │ disabled │
│ 0  │ server    │ default     │ N/A     │ fork    │ 53569    │ 0s     │ 0    │ online    │ 0%       │ 27.4mb   │ endy     │ disabled │
└────┴───────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘

9. Add a module to AppModule to work with the prism

Updated file apps/server/src/app/app.module.ts

import { createNestModule, NestModuleCategory } from '@nestjs-mod/common';

import { AppController } from './app.controller';
import { AppService } from './app.service';
import { PrismaModule } from '@nestjs-mod/prisma';

export const { AppModule } = createNestModule({
  moduleName: 'AppModule',
  moduleCategory: NestModuleCategory.feature,
  imports: [PrismaModule.forFeature({ featureModuleName: 'app' })],
  controllers: [AppController],
  providers: [AppService],
});

10. Add additional methods to AppController to work with AppDemo table data

Updated file apps/server/src/app/app.controller.ts

import { Controller, Delete, Get, Param, Post } from '@nestjs/common';

import { InjectPrismaClient } from '@nestjs-mod/prisma';
import { PrismaClient as AppPrismaClient } from '@prisma/app-client';
import { randomUUID } from 'crypto';
import { AppService } from './app.service';
@Controller()
export class AppController {
  constructor(
    @InjectPrismaClient()
    private readonly appPrismaClient: AppPrismaClient,
    private readonly appService: AppService
  ) {}

  @Get()
  getData() {
    return this.appService.getData();
  }

  @Post('/demo')
  async demoCreateOne() {
    return await this.appPrismaClient.appDemo.create({ data: { name: 'demo name' + randomUUID() } });
  }

  @Get('/demo/:id')
  async demoFindOne(@Param('id') id: string) {
    return await this.appPrismaClient.appDemo.findFirstOrThrow({ where: { id } });
  }

  @Delete('/demo/:id')
  async demoDeleteOne(@Param('id') id: string) {
    return await this.appPrismaClient.appDemo.delete({ where: { id } });
  }

  @Get('/demo')
  async demoFindMany() {
    return await this.appPrismaClient.appDemo.findMany();
  }
}

11. Adding check of new methods in E2E test

Updated file apps/server-e2e/src/server/server.spec.ts

import axios from 'axios';

describe('GET /api', () => {
  let newDemoObject: { id: string };

  it('should return a message', async () => {
    const res = await axios.get(`/api`);

    expect(res.status).toBe(200);
    expect(res.data).toEqual({ message: 'Hello API' });
  });

  it('should create and return a demo object', async () => {
    const res = await axios.post(`/api/demo`);

    expect(res.status).toBe(201);
    expect(res.data.name).toContain('demo name');

    newDemoObject = res.data;
  });

  it('should get demo object by id', async () => {
    const res = await axios.get(`/api/demo/${newDemoObject.id}`);

    expect(res.status).toBe(200);
    expect(res.data).toMatchObject(newDemoObject);
  });

  it('should get all demo object', async () => {
    const res = await axios.get(`/api/demo`);

    expect(res.status).toBe(200);
    expect(res.data.filter((row) => row.id === newDemoObject.id)).toMatchObject([newDemoObject]);
  });

  it('should delete demo object by id', async () => {
    const res = await axios.delete(`/api/demo/${newDemoObject.id}`);

    expect(res.status).toBe(200);
    expect(res.data).toMatchObject(newDemoObject);
  });

  it('should get all demo object', async () => {
    const res = await axios.get(`/api/demo`);

    expect(res.status).toBe(200);
    expect(res.data.filter((row) => row.id === newDemoObject.id)).toMatchObject([]);
  });
});

12. Run e2e tests

Teams

./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=e2e --skip-nx-cache=true --output-style=stream-without-prefixes
Console output
$ ./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 (15s)

——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————


——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————
   ✔  nx run server:build:production (3s)

——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————



——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

 NX   Running target e2e for 2 projects and 1 task they depend on

   →  Executing 1/1 remaining tasks...

   ⠧  nx run server-e2e:e2e

   ✔  2/2 succeeded [0 read from cache]

 PASS   server-e2e  apps/server-e2e/src/server/server.spec.ts
  GET /api
    ✓ should return a message (43 ms)
   ✔  nx run server-e2e:e2e (2s)

——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

 NX   Successfully ran target e2e for 2 projects and 1 task they depend on (21s)

13. Since now some of the methods of the AppController controller use PrismaClient, we need to change the unit tests

Updated file apps/server/src/app/app.controller.spec.ts

import { Test, TestingModule } from '@nestjs/testing';

import { FakePrismaClient, PrismaModule } from '@nestjs-mod/prisma';
import { AppController } from './app.controller';
import { AppService } from './app.service';

describe('AppController', () => {
  const appFeatureName="app";
  let app: TestingModule;

  beforeAll(async () => {
    app = await Test.createTestingModule({
      imports: [
        PrismaModule.forRoot({
          environments: { databaseUrl: 'fake' },
          staticConfiguration: {
            featureName: appFeatureName,
            prismaModule: { PrismaClient: FakePrismaClient },
          },
        }),
      ],
      controllers: [AppController],
      providers: [AppService],
    }).compile();
  });

  describe('getData', () => {
    it('should return "Hello API"', () => {
      const appController = app.get<AppController>(AppController);
      expect(appController.getData()).toEqual({ message: 'Hello API' });
    });
  });
});

14. Run unit tests of the server application

Teams

./node_modules/.bin/nx run server:test --passWithNoTests
Console output
$  ./node_modules/.bin/nx run server:test --passWithNoTests

> nx run server:test --passWithNoTests

 PASS   server  apps/server/src/app/app.service.spec.ts
 PASS   server  apps/server/src/app/app.controller.spec.ts

Test Suites: 2 passed, 2 total
Tests:       2 passed, 2 total
Snapshots:   0 total
Time:        2.07 s, estimated 5 s
Ran all test suites.

——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

 NX   Successfully ran target test for project server (3s)

      With additional flags:
        --passWithNoTests=true

In the next post I will add Swagger documentation generation to the NestJS application and create a REST client that the Angular application will interact with…

Links

https://nestjs.com – official website of the framework
https://nestjs-mod.com – official site of additional utilities
https://github.com/nestjs-mod/nestjs-mod-fullstack – project from the post
https://github.com/nestjs-mod/nestjs-mod-fullstack/commit/a8b7f6ae2660a21730860b384c30da9fc82e9238 – commit to current changes

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *