Below Nest.js + Postgres + TypeORM setup works fine during development building mode and local environment:
import MemberModel from "@DataBase/Models/MemberModel";
import MemberTypeORM_Gateway from "@DataBase/Gateways/MemberTypeORM_Gateway";
import MemberController from "./Controllers/MemberController";
import { Module as NestJS_Module } from "@nestjs/common";
import { TypeOrmModule, TypeOrmModule as TypeORM_Module } from "@nestjs/typeorm";
import type { TypeOrmModuleOptions } from "@nestjs/typeorm";
@NestJS_Module({
imports: [
TypeORM_Module.forRootAsync({
useFactory: (): TypeOrmModuleOptions => ({
type: "postgres",
host: "localhost",
port: 5432,
username: "postgres",
password: "pass1234"
autoLoadEntities: true,
synchronize: true
})
}),
TypeOrmModule.forFeature([ MemberModel ])
],
controllers: [
MemberController
],
providers: [
{
provide: "MemberGateway",
useClass: MemberTypeORM_Gateway
}
]
})
export default class NestJS_ApplicationRootModule {}
Herewith, the docker-compose.yml
file for the local environment is:
version: "3"
services:
Database:
image: postgres
container_name: LocalDatabase
restart: always
ports:
- "5432:5432"
environment:
- POSTGRES_PASSWORD=pass1234
volumes:
- DataBaseData:/var/lib/postgresql/data
volumes:
DataBaseData:
driver: local
The production setup has below differences:
@NestJS_Module({
imports: [
TypeORM_Module.forRootAsync({
useFactory: (): TypeOrmModuleOptions => ({
type: "postgres",
host: "database", // instead of "localhost"
port: 5432,
username: "postgres",
password: "pass1234"
autoLoadEntities: true,
synchronize: false // instead of "true"
})
}),
TypeOrmModule.forFeature([ MemberModel ])
],
// ...
})
export default class NestJS_ApplicationRootModule {}
Herewith the docker-compose.yaml
is:
version: "3"
services:
node_js:
container_name: FrontServer
build: .
ports: [ "8080:8080" ]
depends_on: [ "database" ]
database:
container_name: Database
image: postgres
ports: [ "5432:5432" ]
environment:
- POSTGRES_PASSWORD=pass1234
volumes:
- Database:/data/new.jiseikan.jp
volumes:
Database: {}
When I try get some member
entities, I get the error:
[Nest] 1 - 12/10/2022, 4:23:03 AM ERROR [ExceptionsHandler] relation "members.members" does not exist
QueryFailedError: relation "members.members" does not exist
at PostgresQueryRunner.query (/var/www/example.com/node_modules/typeorm/driver/postgres/PostgresQueryRunner.js:211:19)
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async SelectQueryBuilder.loadRawResults (/var/www/example.com/node_modules/typeorm/query-builder/SelectQueryBuilder.js:2028:25)
at async SelectQueryBuilder.executeEntitiesAndRawResults (/var/www/example.com/node_modules/typeorm/query-builder/SelectQueryBuilder.js:1888:26)
at async SelectQueryBuilder.getRawAndEntities (/var/www/example.com/node_modules/typeorm/query-builder/SelectQueryBuilder.js:638:29)
at async SelectQueryBuilder.getMany (/var/www/example.com/node_modules/typeorm/query-builder/SelectQueryBuilder.js:704:25)
at async B.retrieveAndSortByType (/var/www/example.com/FrontServerEntryPoint.js:1:21266)
at async /var/www/example.com/node_modules/@nestjs/core/router/router-execution-context.js:46:28
at async /var/www/example.com/node_modules/@nestjs/core/router/router-proxy.js:9:17
Let me repeat that it occurs during production mode only.
Of course, I have checked the similar question.
One solution was set synchronize
to true
.
However, the NestJS developers has warned do not do it at production mode.
Setting synchronize: true shouldn't be used in production - otherwise you can lose production data. NestJS Documentation
Anyway, I'll tried it and nothing changed.
The NestJS developer calls it "entity" however according the Clean Architecture, "The entities (enterprise business rules) must depends on nothing" while below class depends on TypeORM. So, the "Member" entity will be at next listing.
import Member from "@EnterpriseBusinessRules/Members/Member";
import WinLossRecords from "@EnterpriseBusinessRules/Members/WinLossRecords";
import {
Entity as TypeORM_Entity,
Column as DatabaseColumn,
PrimaryGeneratedColumn
} from "typeorm";
@TypeORM_Entity({ name: MemberModel.DATA_BASE_TABLE_NAME })
export default class MemberModel implements Member {
public static readonly DATA_BASE_TABLE_NAME: string = "members";
private static readonly defaultWinLossRecordsValue: WinLossRecords = {
wins: 0, knockouts: 0, losses: 0, draws: 0, totalBattles: 0
};
@PrimaryGeneratedColumn("uuid")
public readonly ID!: Member.ID;
@DatabaseColumn({
type: "varchar",
nullable: !Member.DisplayingName.REQUIRED,
length: Member.DisplayingName.MAXIMAL_CHARACTERS_COUNT
})
public readonly displayingName!: string;
@DatabaseColumn({
type: "text",
nullable: !Member.AvatarURI.REQUIRED
})
public readonly avatarURI!: string;
@DatabaseColumn({
type: "text",
nullable: !Member.LargePhotoURI.REQUIRED
})
public readonly largePhotoURI!: string;
@DatabaseColumn({
type: "enum",
enum: Object.values(Member.Types),
nullable: !Member.Type.REQUIRED
})
public readonly type!: Member.Types;
@DatabaseColumn({
type: "simple-json",
default: MemberModel.defaultWinLossRecordsValue
})
public readonly domesticWinLossRecords!: WinLossRecords;
@DatabaseColumn({
type: "simple-json",
default: MemberModel.defaultWinLossRecordsValue
})
public readonly overseasWinLossRecords!: WinLossRecords;
@DatabaseColumn({
type: "simple-json",
default: MemberModel.defaultWinLossRecordsValue
})
public readonly amateurWinLossRecords!: WinLossRecords;
@DatabaseColumn({
type: "simple-array",
nullable: !Member.Ranks.REQUIRED
})
public readonly ranks?: Array<string>;
@DatabaseColumn({
type: "date",
nullable: !Member.BirthDate__ISO8601.REQUIRED
})
public readonly birthDate__ISO8601?: string;
@DatabaseColumn({
type: "varchar",
nullable: !Member.Birthplace.REQUIRED,
length: Member.Birthplace.MAXIMAL_CHARACTERS_COUNT
})
public readonly birthplace!: string;
@DatabaseColumn({
type: "simple-array",
nullable: !Member.MessageSplitToParagraphs.REQUIRED
})
public readonly messageSplitToParagraphs?: Array<string>;
@DatabaseColumn({
type: "text",
nullable: !Member.PersonalBlogURI.REQUIRED
})
public readonly personalBlogURI!: string;
@DatabaseColumn({
type: "simple-json",
default: {}
})
public readonly socialNetworkProfilesURIs!: Member.SocialNetworkProfilesURIs;
}
import type WinLossRecords from "./WinLossRecords";
type Member = {
ID: Member.ID;
displayingName: string;
avatarURI: string;
largePhotoURI: string;
type: Member.Types;
domesticWinLossRecords: WinLossRecords;
overseasWinLossRecords: WinLossRecords;
amateurWinLossRecords: WinLossRecords;
ranks?: Array<string>;
birthDate__ISO8601?: string;
birthplace?: string;
messageSplitToParagraphs?: Array<string>;
personalBlogURI?: string;
socialNetworkProfilesURIs: Member.SocialNetworkProfilesURIs;
};
namespace Member {
export type ID = string;
export namespace ID {
export const TYPE: StringConstructor = String;
export const REQUIRED: boolean = true;
export const MAXIMAL_CHARACTERS_COUNT: number = 1;
export function isValid(potentialID: unknown): potentialID is ID {
return typeof potentialID === "string" && potentialID.length >= MAXIMAL_CHARACTERS_COUNT;
}
}
export namespace DisplayingName {
export const TYPE: StringConstructor = String;
export const REQUIRED: boolean = true;
export const MINIMAL_CHARACTERS_COUNT: number = 1;
export const MAXIMAL_CHARACTERS_COUNT: number = 255;
}
// And so on.
}
export default Member;
What I don't like is the MemberModel has been set twice: at entities
property and also at TypeOrmModule.forFeature
. Can I safely leave just one of?
@NestJS_Module({
imports: [
TypeORM_Module.forRootAsync({
useFactory: (): TypeOrmModuleOptions => ({
type: "postgres",
host: ConfigurationRepresentative.isLocalExecutionEnvironment ? "localhost" : "database",
port: 5432,
username: "postgres",
password: "pass1234"
autoLoadEntities: ConfigurationRepresentative.isLocalExecutionEnvironment,
...ConfigurationRepresentative.isLocalExecutionEnvironment ? null : { entities: [ MemberModel ] },
synchronize: ConfigurationRepresentative.isLocalExecutionEnvironment
})
}),
// Duplicate?
TypeOrmModule.forFeature([ MemberModel ])
]
// ...
})
export default class NestJS_ApplicationRootModule {}
I would say that your production database is not identical to your development database.
Note that synchronize, which you define as true in development, automatically performs migrations so that your database is identical to your modeling.
Once you disable this, it is up to the developer or DBA to manually create a migration to update the database.
Using liquibase, you can easily compare two databases and see the difference between them. I suggest you do this on your development and production bench.