국밥인 게시판&게시글을 구현하자

HanSH·2024년 2월 1일

NestJS

목록 보기
15/29

NestJS로 갈아탄 이유

Spring Boot와 비교하면...

스프링도 게시판, 게시글 구현까지는 쉬웠다! 여기까지는...
구현은 했는데 ① JWT 인증에서 filter 적용하는 방법을 최신버전 docs가 잘 이해되지 않았고, ② 구버전으로 돌아가서 한다 해도 방법을 이해 못하겠어서 스프링을 포기했었다.
filter만 어떻게 하면 될 거 같은데... 하던게 거의 한주

Nest는 Guard 하나만으로도 할 수 있어서 편했다!
Guard를 건너뛸 수 있는 방법도 있어서 편했다!

FastAPI와 비교하면...

FastAPI도 꽤 편했다. 다만 여기서는 내가 코드를 잘 관리하지 못해서 파일이 너무 중구난방한 것.
추가로 이때는 docs를 안보고 다른 사람이 짠거 그대로 들고와서 적용했던거라 작동 방식은 몰랐다.
↑ 이거 때문에 Velog 작성을 하고 있는 중!
새로운 기술을 배워보기도 해야해서 문서로 남긴다.

기본 테이블 생성

엔드포인트 생성

nest g resource boards ← 게시판
nest g resource essays ← 게시글
nest g resource comments ← 댓글

그 다음 각각의 module에 가서 TypeOrmModule.forFeature([<Entity>])를 추가해주자.
안쓰면 Repository 못찾는다는 오류가 발생한다.

Entity 수정

  1. 유저 1 : 게시판 n
  2. 유저 1 : 게시글 n
  3. 게시판 1 : 게시글 n
  4. 게시글 1 : 댓글 n

이에 맞춰 entity를 수정하자.

users.entity.ts
@Entity({name: 'user'}) // 테이블의 기본 이름 설정
export class User {
    @PrimaryGeneratedColumn()
    uid: number;

    @Column()
    id: string;

    @Column()
    password: string;

    @Column()
    email: string;

    @Column({default: 'default'})
    nickname: string;

    @CreateDateColumn()
    createdAt: Date;

    @OneToMany(
        () => Essay,
        essay => essay
    )
    essays: Essay[];
}
boards.entity.ts
@Entity("board")
export class Board {
    @PrimaryGeneratedColumn()
    uid: number;

    @Column()
    name: string;

    @Column()
    describe: string;

    @CreateDateColumn()
    createdAt: Date;

    @ManyToOne(
        () => User,
        user => user.boards
    )
    owner: User;

    @OneToMany(
        () => Essay,
        essay => essay.board
    )
    essays: Essay[];
}
essays.entity.ts
@Entity('essay')
export class Essay {
    @PrimaryGeneratedColumn()
    uid: number;
    
    @Column()
    title: string;

    @Column({ 
        length: 1000
     })
    content: string;

    @CreateDateColumn()
    createdAt: Date;

    @UpdateDateColumn()
    updatedAt: Date;

    @Column({ default: 0 })
    likeCount: number;

    @Column({ default: 0 })
    viewCount: number;

  
    @ManyToOne(
        () => User,
        user => user.boards
    )
    owner: User;
  
    @ManyToOne(
        () => Board,
        board => board.essays
    )
    board: Board;
  
    @OneToMany(
        () => Comment,
        comment => comment.essay
    )
    comments: Comment[]
}
comments.entity.ts
@Entity('comment')
export class Comment {
    @PrimaryGeneratedColumn()
    cid: number;

    @Column({default: ""})
    content: string;
    
    @CreateDateColumn()
    createdAt: Date;

    @UpdateDateColumn()
    updatedAt: Date;

    @ManyToOne(
        () => Essay,
        essay => essay.comments
    )
    essay: Essay;

    @ManyToOne(
        () => User,
        user => user.comments
    )
    owner: User;
}

모듈 수정

  1. Service를 다른 모듈에서 사용하는 경우, export 해주는 것만 까먹지 않으면 된다.
  2. Orm을 쓰는 경우, TypeOrmModule.forFeature을 import 하는 것을 잊지 말자.
  3. 필요한 모듈들을 import하면 된다.
boards.module.ts
@Module({
  imports: [
    AuthModule,
    UsersModule,
    TypeOrmModule.forFeature([Board])
  ],
  controllers: [BoardsController],
  providers: [BoardsService],
  exports: [
    BoardsService // essay module에서 사용
  ]
})
export class BoardsModule {}
essays.module.ts
@Module({
  imports: [
    AuthModule,
    BoardsModule,
    UsersModule,
    TypeOrmModule.forFeature([Essay])
  ],
  controllers: [EssaysController],
  providers: [EssaysService],
  exports: [
    EssaysService,
  ]
})
export class EssaysModule {}
comments.module.ts
@Module({
  imports: [
    UsersModule,
    EssaysModule,
    TypeOrmModule.forFeature([Comment])
  ], 
  controllers: [CommentsController],
  providers: [CommentsService],
})
export class CommentsModule {}

Service와 Controller

여기는 입맛에 맞게 작성해주자.
코드가 너무 길어지므로 구현은 사용자가 알아서 하자.

중요한 부분은 아래와 같다. 이 부분만 잘 써주면 된다.

  // 생성자
constructor(
  // 레포지토리(데이터베이스) 연결
  @InjectRepository(Comment)
  private commentRepository: Repository<Comment>,

  // 필요한 서비스들 추가
  private essaysService: EssaysService, 
  private usersService: UsersService,
) {}
// 쿼리
const comments = await this.commentRepository.find({
  where: { // 검색 조건은 어떻게 돼?
    essay: essay,
  },
  order: { // 정렬 순서는 어떻게 돼?
    createdAt: 'DESC',
  },
  skip: (page - 1) * this.pageSize, // 어디서부터 가져올거야?
  take: this.pageSize, // 몇 개 가져올거야?
})

※ Repository에서 find를 할 때는 await 키워드를 빼먹지 말자. 기본이 Promise<Type>이라 엔티티의 값으로 넣을 때 Promise는 Type과 다릅니다 오류가 발생한다.

결과

DBeaver로 확인을 해보았다.
원하는 entity 그대로 들어감을 볼 수 있다.

FastAPI보다 NestJS가 조금 더 편한 것 같다.
Guard, Interceptor를 자유자재로 사용할 수 있다는게 좋다는 점?

profile
저는 말하는 싹 난 감자입니다

0개의 댓글