[NestJs] drizzle-orm 적용해보기

star_moon_cloud_k·2024년 7월 14일

NestJs

목록 보기
5/5

개발을 시작할 때 많은 것을 고민한다. 그 중 새로운 기술을 접하게 되었을 때 가장 고민되는것 같다. 항상 새로운 기술들은 나오고, 그 전에 사용하던 기술들은 항상 다 써보지도 못하고 적응할 때 쯤에 새로운 기술이 나오는 것 같다는 생각이 들어서 아닐까?

드리즐 홈페이지 https://orm.drizzle.team/
깃 허브 https://github.com/drizzle-team/drizzle-orm

드리즐(Drizzle)은 SQL 데이터베이스와 상호작용하기 위한 ORM(Object-Relational Mapping)라이브러리이다.

뭐 그냥 type-orm, prisma 와 같이 typescript 생태계에서 새로운 라이브러리라고 들어 시도해보고 싶었다.

최근 프로젝트를 하나 진행하게 되었는데, 나는 항상 프로젝트를 할 때 마다 새로운 라이브러리 도입이나, 기술들을 적용해보고 싶어서 이상한것들을 찾아보고 적용해본다.

그게 이번엔 drizzle-orm이 된 것 같다. 그 외에도 최근에 typescript 생태계를 시작하게 되었기 때문에, class-validator밖에 사용을 못 해봤는데, zod를 사용해서 validator를 사용할 것 같다.

드리즐 맛보기

데이터 접근

// Access your data
await db
.select()
.from(countries)
.leftJoin(cities, eq(cities.countryId, countries.id))
.where(eq(countries.id, 10))

드리즐의 메뉴얼 첫 페이지에 있는 Select 코드이다.

  • typeorm에서 사용하는 QueryBuilder() 처럼 억지 부리는 느낌이 아니라 그냥 SQL 코드 같다

스키마 선언

// manage your schema
export const countries = pgTable('countries', {
  id: serial('id').primaryKey(),
  name: varchar('name', { length: 256 }),
});

export const cities = pgTable('cities', {
  id: serial('id').primaryKey(),
  name: varchar('name', { length: 256 }),
  countryId: integer('country_id').references(() => countries.id),
});

pgTable이라는 함수를 사용해서 테이블의 이름을 정하고, 각 데이터의 객체를 어떻게 정의할 지 작성한다.

  • 확실히 SQL에 친화적이긴 하다.

findMany

const result = await db.query.users.findMany({
	with: { 
		posts: true 
	},
});

그렇다고 해서 findMany 와 같이 데이터를 불러오는 함수들도 제공해준다.

드리즐 시작하기

간단하게 nestjs의 프로젝트를 하나 만들고, yarn add로 드리즐 라이브러리를 불러온다.
https://orm.drizzle.team/docs/get-started-postgresql#node-postgres
node-postgres를 기반으로 코드를 작성해본다.

yarn add drizzle-orm pgyarn add -D drizzle-kit @types/pg

데이터베이스 연결

client, pool 두 가지의 방식으로 연결해 접근하는 방법이 있다.
나는 pool 방식으로 연결해볼 생각이다.

import { pgTable, serial, text, varchar } from "drizzle-orm/pg-core";
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";

const pool = new Pool({
  connectionString: "postgres://user:password@host:port/db",
});

// or
const pool = new Pool({
  host: "127.0.0.1",
  port: 5432,
  user: "postgres",
  password: "password",
  database: "db_name",
});

const db = drizzle(pool);

나는 지금까지 spring 프레임워크에만 익숙하게 개발하다가 최근에 nestjs 쪽으로 넘어오게 됐는데 아직 익숙하지 않은게 너무 많다...

특히 이 drizzle의 구조 자체가 도대체 왜 이렇게 만들어 진거지.. 라는 생각을 많이 했다..

그래도 nestjs를 위해 간단하게 구조화 시켜놓은 라이브러리를 발견했다.
https://www.npmjs.com/package/@knaadh/nestjs-drizzle-pg

project 구조

📦 <project root>
└ 📜 drizzle.config.ts
└ 📂 src
└ 📂 db 
	  └ 📜 schema.ts
	  └ 📜 drizzle.module.ts

drizzle.module.ts

import { DrizzlePGModule } from "@knaadh/nestjs-drizzle-pg"
import { Module } from "@nestjs/common"
import * as schema from "./schema"

export const PG_CONNECTION = "PG_CONNECTION" // ignore that it is not separate file

@Module({
  imports: [
    DrizzlePGModule.registerAsync({
      tag: "DB",
      useFactory() {
        return {
          pg: {
            connection: "pool",
            config: {
              host: process.env.DB_HOST,
              user: process.env.DB_USER,
              password: process.env.DB_PASSWORD,
              database: process.env.DB_NAME,
              port: parseInt(process.env.DB_PORT),
            },
          },
          config: { schema: { ...schema } },
        }
      },
    }),
  ],
})
export class DrizzleModule {}
  • .env파일에는 환경 값 저장을 해두고 useFactory()로 pg 객체를 하나 만들어준다.
  • config에 schema는 데이터베이스 테이블 스키마에 대해 연결하는 파일을 넣어준다.
#DB
PORT=3000
DB_HOST=localhost
DB_USER=postgres
DB_PASSWORD=postgres
DB_PORT=5432
DB_NAME=postgres

간단하게 적용하기

import * as schema from "@drizzle/schema"
import { Product, ProductTags, Tag } from "@drizzle/schema"
import { Inject, Injectable } from "@nestjs/common"
import { getProductlistSchema } from "@product/common/product-zod.dto"
import { AddProductReq } from "@product/common/product.dto"
import { eq, getTableColumns, sql } from "drizzle-orm"
import { NodePgDatabase } from "drizzle-orm/node-postgres"

@Injectable()
export class ProductService {
  constructor(@Inject("DB") private db: NodePgDatabase<typeof schema>) {}

  async getProductList() {
    const result = await this.db
      .select({
        product: getTableColumns(Product),
        tags: sql<string[]>`array_agg(${Tag.name})`,
      })
      .from(Product)
      .leftJoin(ProductTags, eq(ProductTags.productId, Product.id))
      .leftJoin(Tag, eq(ProductTags.tagId, Tag.id))
      .groupBy(Product.id)
    
    const res = getProductlistSchema.parse(result)
    return res
  }

  async addProductData(payload: AddProductReq) {
    const [data] = await this.db
      .insert(schema.Product)
      .values({ ...payload })
      .returning()
    return data
  }
}

지금까지 느낀점

  • 데이터를 불러올 때나 저장할 때 간단하게 귀찮게 해줘야하는게 적긴하다.
  • schema를 만들어서 연결하는 과정이 생각보다 빠르긴 한데, sql 위주의 코드를 작성하는 느낌이 강하게 든다.
  • 프로젝트에 처음 적용할 때 가장 중요한 구글링에 대한 내용이 상당히 적어 확실히 적용하기 어렵다는 느낌이 들었다. (심지어 챗지피티도 제대로 모른다.)
  • 매뉴얼이 생각보다 부실하다.
    - 아직 nestjs 자체에 적응이 잘 안된것도 있을것이지만, 이것만 보고 적용하기 좀 어려웠다.
profile
자유로운 개발자

0개의 댓글