본문 바로가기
framework/nest.js

# Prisma vs Kysely

by choi-dev 2025. 11. 23.

스프링부트를 처음 사용했을 때는 ORM으로 JPA를 사용했었습니다. nest.js도 처음 사용했을 때, 워낙에 많이 들었던 TypeORM을 사용해 사내 프로젝트를 진행했었는데요. 사용하다보니 정말 불편한 점도 많았고 성향에 따라서 많이 갈리겠구나라는 생각을 정말 많이 했습니다.

 

처음엔 로우 쿼리에 대해 반신반의하였습니다. ORM이 알아서 다 짜주는데 왜 내가 만들어야하지? 가 신입 때의 생각이었고 시간이 흘러 초-중급 개발자가 되고나니 ORM의 한계에 부딪히기 시작했습니다. 이후로는 ORM의 find()와 같은 간단한 쿼리를 쓸 수 있음에도 직접 로우를 짜는 것이 편한 지경에 이르게 된 것 같습니다.

 

이 때 느낀 것은 아, 나는 형태만 갖춰주고 ORM이 구성해주는 걸 좋아하는 개발자가 아닌 시간은 걸리더라도 직접 SQL문을 구성해 만드는 걸 좋아하는 사람이란 것을 깨달았습니다. 현재 개인 프로젝트로 간단히 무언가 해보려고 하고 과거 불편했던 점을 고려하며 세팅을 진행하고 있는데, TypeORM이 아닌 다른 ORM 모듈에 대해 리서치를 진행하고 있습니다.

 

해외 레딧에는 뜨거운 감자로 이슈가 되고 있는 듯한 글들을 여러 개 보았습니다. ORM이라는 것이 결국 DB ↔ 프레임워크 간의 간극을 줄여주기는 하나 완벽히 메꾸어주지는 못한다는 걸 느꼈습니다. 어쨌든 제 개인 프로젝트에서는 이 둘 중 한 가지를 선택하고 싶은데 저는 Kysely를 사용하기로 결정하였습니다. 선택했던 이유는 간단합니다. CTE와 서브쿼리 같은 복잡한 쿼리에 대해 처리가 잘 되어있고 그냥 저와 성향이 비슷한 그런 느낌이었기 때문입니다.

 

다만 마이그레이션에 대해서는 따로 존재하지 않는 듯해 쿼리(Kysely) + 마이그레이션(knex)의 조합으로 프로젝트를 진행할 예정입니다.

 

1. 설치

npm install kysely

먼저 Kysely를 프로젝트에 설치합니다.

 

이런 식으로 파일과 디렉토리를 먼저 생성해주세요.

 

구조를 조금 설명드리자면 schema는 엔티티 정의처럼 테이블의 스키마를 관리하는 곳을 의미하고 kysely.ts는 데이터베이스 연결 Config를 적으시는 부분이라 생각하면 됩니다. index.ts는 정의한 스키마를 하나로 관리하는 영역입니다. 이에 대한 모듈을 AppModule이 알 수 있도록 모듈로 적은 것이 database.module.ts입니다.

 

// kysely.ts
import { Kysely, MysqlDialect } from 'kysely';
import { createPool } from 'mysql2';
import { Schema } from '.';
require('dotenv').config();

console.log(
  `[DB] Connecting to MySQL on ${process.env.ENVIRONMENT == 'production' ? process.env.PRD_DB_HOST : process.env.DEV_DB_HOST} ${process.env.ENVIRONMENT == 'production' ? process.env.PRD_DB_PORT : process.env.DEV_DB_PORT} (db=${process.env.ENVIRONMENT == 'production' ? process.env.PRD_DB_NAME : process.env.DEV_DB_NAME})`,
);

export const db = new Kysely<Schema>({
  dialect: new MysqlDialect({
    pool: createPool({
      host:
        process.env.ENVIRONMENT == 'production'
          ? process.env.PRD_DB_HOST
          : process.env.DEV_DB_HOST,
      user:
        process.env.ENVIRONMENT == 'production'
          ? process.env.PRD_DB_USER
          : process.env.DEV_DB_USER,
      password:
        process.env.ENVIRONMENT == 'production'
          ? process.env.PRD_DB_PASSWORD
          : process.env.DEV_DB_PASSWORD,
      database:
        process.env.ENVIRONMENT == 'production'
          ? process.env.PRD_DB_NAME
          : process.env.DEV_DB_NAME,
      port:
        process.env.ENVIRONMENT == 'production'
          ? Number(process.env.PRD_DB_PORT)
          : Number(process.env.DEV_DB_PORT),
    }),
  }),
});

저는 운영과 개발을 나눌 생각이기 때문에 삼항 연산으로 분기 처리 진행하였습니다.

 

// index.ts
import { AuthTable } from './schema/auth';
import { UsersTable } from './schema/users';

export interface Schema {
  users: UsersTable;
  auth: AuthTable;
}

앞으로 테이블이 추가되고 스키마가 추가된다면 다음 영역을 추가해줍니다.

 

// users.ts
export interface UsersTable {
  user_idx: string;
  username: string;
  phone_number: string | null;
  birthday: string | null;
  email: string | null;
  gender: boolean;
  status: string;
  is_agree: boolean;
  reg_dt: Date;
  udt_dt: Date | null;
  del_dt: Date | null;
  ci: string | null;
}

각 테이블의 스키마는 다음과 같이 관리합니다. NOT NULL은 자료형을 그대로, NULL은 null이 될 수 있기에 조정하였습니다. 제가 생각하는 구조는 이 테이블을 예시로, 유저와 관련된 이를테면 주소를 관리하는 등등 유저 테이블과 연관되는 것이라면 해당 인터페이스에 이어서 붙이는 구조로 진행할 예정입니다.

 

// database.module.ts
import { Module } from '@nestjs/common';
import { db } from './kysely';

@Module({
  providers: [
    {
      provide: 'DB',
      useValue: db,
    },
  ],
  exports: ['DB'],
})
export class DatabaseModule {}

nest.js가 실행되면서 데이터베이스를 읽어낼 수 있도록 모듈로 작성합니다.

 

// app.module.ts
import { Module } from '@nestjs/common';
import { DatabaseModule } from './db/database.module';

@Module({
  imports: [DatabaseModule],
  controllers: [],
  providers: [],
})
export class AppModule {}

앱 모듈이 이를 감지하고 실행시킬 수 있도록 합니다. 

 

데이터베이스 연결까지 완료하였습니다.

'framework > nest.js' 카테고리의 다른 글

nest install  (0) 2025.04.23