DEV Community

ILshat Khamitov
ILshat Khamitov

Posted on

Connecting PrismaORM to the NestJS-mod application and checking its operation via REST

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

The verification of the work takes place through the launch of E2E tests for REST-endpoints.

1. Install all the necessary packages

Commands

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

# Install all need dev-dependencies
npm i --save-dev prisma
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

2. Adding 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),
        },
      }),
    ],
  },
});
Enter fullscreen mode Exit fullscreen mode

3. We create documentation for the project and simultaneously create additional code and scripts to work with PrismaORM

Commands

# Build all applications and library
npm run build

# Generate markdown report
npm run docs:infrastructure
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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),
        },
      }),
    ],
  },
});
Enter fullscreen mode Exit fullscreen mode

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

Commands

# 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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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

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")
}

Enter fullscreen mode Exit fullscreen mode

7. Since by default the pm2 configuration contains the launch of an already built 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"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

8. Restarting all applications

Commands

# Stop all applications
npm run pm2:stop

# Start all applications
npm run pm2:start
Enter fullscreen mode Exit fullscreen mode

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 │
└────┴───────────┴─────────────┴─────────┴─────────┴──────────┴────────┴──────┴───────────┴──────────┴──────────┴──────────┴──────────┘
Enter fullscreen mode Exit fullscreen mode

9. Adding a module for working with the Prisma to the AppModule

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],
});
Enter fullscreen mode Exit fullscreen mode

10. Adding additional methods to the AppController to work with the data of the AppDemo table

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();
  }
}
Enter fullscreen mode Exit fullscreen mode

11. Adding verification of new methods in the 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([]);
  });
});
Enter fullscreen mode Exit fullscreen mode

12. Launching e2e tests

Commands

./node_modules/.bin/nx run-many --exclude=@nestjs-mod-fullstack/source --all -t=e2e --skip-nx-cache=true --output-style=stream-without-prefixes
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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' });
    });
  });
});
Enter fullscreen mode Exit fullscreen mode

14. Running unit tests of the server application

Commands

./node_modules/.bin/nx run server:test --passWithNoTests
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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

Links

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

Top comments (0)