I have developed features for creating posts using NestJs and TypeORM. Everything works fine in the development environment, but when deployed, dependency errors occur only in the production environment.
While user registration, login, and logout functionalities work perfectly even in the production environment, the creating posts result in a 500 error accompanied by a cyclic dependency error.
Here is the server log for reference:
2024-06-15 03:31:58 query: SELECT "c"."id" AS "c_id", "c"."created_at" AS "c_created_at", "c"."username" AS "c_username", "c"."password" AS "c_password", "c"."character_nickname" AS "c_character_nickname", "c"."refresh_token" AS "c_refresh_token", "c"."refresh_token_expires_at" AS "c_refresh_token_expires_at" FROM "users" "c" WHERE (("c"."id" = $1) AND ("c"."username" = $2) AND ("c"."character_nickname" = $3)) LIMIT 1 -- PARAMETERS: [1,"test","tester"] 2024-06-15 03:31:58 query: START TRANSACTION 2024-06-15 03:31:58 query: ROLLBACK 2024-06-15 03:31:58 [Nest] 30 - 06/14/2024, 6:31:58 PM ERROR [FreeBoardService] Failed to create free post for user "test" 2024-06-15 03:31:58 TypeORMError: Cyclic dependency: "c" 2024-06-15 03:31:58 at visit (/app/node_modules/typeorm/persistence/SubjectTopoligicalSorter.js:147:23) 2024-06-15 03:31:58 at visit (/app/node_modules/typeorm/persistence/SubjectTopoligicalSorter.js:164:21) 2024-06-15 03:31:58 at SubjectTopoligicalSorter.toposort (/app/node_modules/typeorm/persistence/SubjectTopoligicalSorter.js:143:17) 2024-06-15 03:31:58 at SubjectTopoligicalSorter.sort (/app/node_modules/typeorm/persistence/SubjectTopoligicalSorter.js:53:45) 2024-06-15 03:31:58 at SubjectExecutor.execute (/app/node_modules/typeorm/persistence/SubjectExecutor.js:91:108) 2024-06-15 03:31:58 at EntityPersistExecutor.execute (/app/node_modules/typeorm/persistence/EntityPersistExecutor.js:140:36) 2024-06-15 03:31:58 at process.processTicksAndRejections (node:internal/process/task_queues:95:5) 2024-06-15 03:31:58 at async l.createFreePost (/app/.next/server/pages/api/[...catchAll].js:1:18130) 2024-06-15 03:31:58 at async /app/node_modules/@nestjs/core/router/router-execution-context.js:46:28 2024-06-15 03:31:58 at async /app/node_modules/@nestjs/core/router/router-proxy.js:9:17 2024-06-15 03:31:58 [Nest] 30 - 06/14/2024, 6:31:58 PM ERROR [FreeBoardService] Error details: Cyclic dependency: "c"
Here are the steps I’ve taken to resolve this error:
- Recreated the schema of the production DB to be identical to the local DB.
- Used forwardRef to handle dependency injection between modules and services.
Despite these efforts, the dependency error persists.
Does anyone know why it works fine in the development environment but fails in the production environment?
Here is the relevant code:
// user.entity.ts
import {
BaseEntity,
Column,
CreateDateColumn,
Entity,
PrimaryGeneratedColumn,
Unique,
OneToMany,
} from 'typeorm';
import type { Relation } from 'typeorm';
import { FreePost } from '../boards/free/freePost.entity';
import { Comment } from '../comments/comment.entity';
@Entity('users')
@Unique(['username'])
export class User extends BaseEntity {
@PrimaryGeneratedColumn({ name: 'id' })
id: number;
@CreateDateColumn({
name: 'created_at',
type: 'timestamp with time zone',
})
createdAt: Date;
@Column({ name: 'username', type: 'varchar', length: 255, nullable: false })
username: string;
@Column({ name: 'password', type: 'varchar', length: 255, nullable: false })
password: string;
@Column({
name: 'character_nickname',
type: 'varchar',
length: 255,
nullable: false,
})
characterNickname: string;
@Column({ name: 'refresh_token', type: 'text', nullable: true })
refreshToken: string | null;
@Column({
name: 'refresh_token_expires_at',
type: 'timestamp with time zone',
nullable: true,
})
refreshTokenExpiresAt: Date | null;
@OneToMany(() => FreePost, (post) => post.writer)
posts: Relation<FreePost[]>;
@OneToMany(() => Comment, (comment) => comment.writer)
comments: Relation<Comment[]>;
}
// freePost.entity.ts
import {
Entity,
BaseEntity,
PrimaryGeneratedColumn,
CreateDateColumn,
Column,
ManyToOne,
OneToMany,
JoinColumn,
} from 'typeorm';
import type { Relation } from 'typeorm';
import { User } from '../../auth/user.entity';
import { Comment } from '../../comments/comment.entity';
@Entity('free_posts')
export class FreePost extends BaseEntity {
@PrimaryGeneratedColumn({ name: 'id' })
id: number;
@CreateDateColumn({
name: 'created_at',
type: 'timestamp with time zone',
})
createdAt: Date;
@ManyToOne(() => User, (user) => user.posts)
@JoinColumn({ name: 'writer_id' })
writer: Relation<User>;
@Column({ name: 'title', type: 'varchar', length: 255, nullable: false })
title: string;
@Column({ name: 'content', type: 'text', nullable: false })
content: string;
@OneToMany(() => Comment, (comment) => comment.post)
comments: Relation<Comment[]>;
}
// freePost.controller.ts
@Post()
@UseGuards(AuthGuard('jwt'))
createFreePost(
@Body(ValidationPipe) createFreePostDto: CreateFreePostDto,
@GetUser() user: User,
): Promise<FreePost> {
return this.freeBoardService.createFreePost(createFreePostDto, user);
}
// freeBoard.service.ts
async createFreePost(
createFreePostDto: CreateFreePostDto,
user: User,
): Promise<FreePost> {
const { title, content } = createFreePostDto;
const post = this.freeBoardRepository.create({
writer: user,
title,
content,
});
try {
await this.freeBoardRepository.save(post);
console.log(post);
return post;
} catch (error: any) {
logger.error(
`Failed to create free post for user "${user.username}"`,
error.stack,
);
logger.error(`Error details: ${error.message}`);
throw new InternalServerErrorException('Failed to create free post');
}
}
// jwt.strategy.ts
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { AccessTokenPayload } from '@/types/back/auth.type';
import { InjectRepository } from '@nestjs/typeorm';
import { User } from './user.entity';
import { Repository } from 'typeorm';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
constructor(
@InjectRepository(User)
private userRepository: Repository<User>,
) {
super({
secretOrKey: process.env.JWT_SECRET,
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
});
}
async validate(payload: AccessTokenPayload) {
const { id, username, characterNickname } = payload;
const user = await this.userRepository.findOne({
where: { id, username, characterNickname },
});
if (!user) {
throw new UnauthorizedException();
}
return user;
}
}
// typeorm.config.ts
import { TypeOrmModuleOptions } from '@nestjs/typeorm';
import { User } from './server/auth/user.entity';
import config from 'config';
import DbConfig from './types/back/config.type';
import { FreePost } from './server/boards/free/freePost.entity';
import { Comment } from './server/comments/comment.entity';
const dbConfig = config.get<DbConfig>('db');
export const typeORMConfig: TypeOrmModuleOptions = {
type: 'postgres',
host: process.env.DB_HOSTNAME || dbConfig.host,
port: dbConfig.port,
username: process.env.DB_USERNAME || dbConfig.username,
password: process.env.DB_PASSWORD || dbConfig.password,
database: process.env.DB_NAME || dbConfig.database,
entities: [User, FreePost, Comment],
synchronize: process.env.NODE_ENV !== 'production',
logging: true,
};
< What I’ve Tried >
- Recreated the production DB tables to match the development DB tables.
- Used forwardRef to handle dependency injection between modules and services.
< What I’m Expecting >
To have the same functionality in the production environment as in the development environment.
KBH is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.