Drizzle와 Turso 사용하기

Turso와 Drizzle ORM 설정하기

이 튜토리얼에서는 Turso와 함께 Drizzle ORM을 사용하는 방법을 설명합니다.

This guide assumes familiarity with:
  • Drizzle ORM과 Drizzle kit을 설치해야 합니다. 다음 명령어를 실행하여 설치할 수 있습니다:
npm
yarn
pnpm
bun
npm i drizzle-orm
npm i -D drizzle-kit
  • 환경 변수를 관리하기 위해 dotenv 패키지를 설치해야 합니다. 이 패키지에 대한 자세한 내용은 여기에서 확인할 수 있습니다.
npm
yarn
pnpm
bun
npm i dotenv
  • @libsql/client 패키지를 설치해야 합니다. 이 패키지에 대한 자세한 내용은 여기에서 확인할 수 있습니다.
npm
yarn
pnpm
bun
npm i @libsql/client
  • Turso CLI를 설치해야 합니다. 자세한 내용은 문서를 참고하세요.

TursolibSQL을 기반으로 한 SQLite 호환 데이터베이스입니다. libSQL은 SQLite의 오픈 소스 포크입니다. Turso는 조직당 수십만 개의 데이터베이스로 확장할 수 있으며, 마이크로초 단위의 지연 시간으로 접근할 수 있도록 자체 서버를 포함한 모든 위치로의 복제를 지원합니다. Turso의 개념에 대해 더 자세히 알아보려면 여기를 참고하세요.

Drizzle ORM은 libSQL 드라이버를 기본적으로 지원합니다. SQL 방언과 방언별 드라이버 및 구문을 수용하며, 가장 많이 사용되는 SQLite 스타일의 all, get, values, run 쿼리 메서드 구문을 대부분 반영합니다.

Turso 데이터베이스를 설정하는 방법은 공식 문서를 참고하세요.

Turso 회원가입 또는 로그인

회원가입:

turso auth signup

로그인:

turso auth login

새로운 데이터베이스 생성

turso db create <DATABASE_NAME> 명령어를 실행하여 새로운 데이터베이스를 생성합니다:

turso db create drizzle-turso-db

생성된 데이터베이스 정보를 확인하려면 다음 명령어를 실행하세요:

turso db show drizzle-turso-db

인증 토큰 생성하기

데이터베이스용 인증 토큰을 생성하려면 다음 커맨드를 실행하세요:

turso db tokens create drizzle-turso-db

이 커맨드와 관련된 옵션에 대해 더 자세히 알아보려면 공식 문서를 참고하세요.

환경 변수 업데이트

.env 또는 .env.local 파일에 연결 URL과 인증 토큰을 추가하세요.

TURSO_CONNECTION_URL=
TURSO_AUTH_TOKEN=

Drizzle ORM을 데이터베이스에 연결하기

src/db 디렉토리에 index.ts 파일을 생성하고 데이터베이스 설정을 구성합니다:

import { config } from 'dotenv';
import { drizzle } from 'drizzle-orm/libsql';

// .env 또는 .env.local 파일에서 환경 변수 로드
config({ path: '.env' });

// Drizzle ORM을 사용해 데이터베이스 연결 설정
export const db = drizzle({
  connection: {
    url: process.env.TURSO_CONNECTION_URL!, // TURSO 데이터베이스 연결 URL
    authToken: process.env.TURSO_AUTH_TOKEN!, // TURSO 인증 토큰
  },
});

이 코드는 Drizzle ORM을 사용해 데이터베이스에 연결하는 기본 설정을 보여줍니다. 환경 변수 파일(.env 또는 .env.local)에서 TURSO_CONNECTION_URLTURSO_AUTH_TOKEN을 읽어와 데이터베이스 연결을 구성합니다.

테이블 생성하기

src/db 디렉토리에 schema.ts 파일을 생성하고 테이블을 선언합니다:

import { sql } from 'drizzle-orm';
import { integer, sqliteTable, text } from 'drizzle-orm/sqlite-core';

// 사용자 테이블 정의
export const usersTable = sqliteTable('users', {
  id: integer('id').primaryKey(), // 기본 키
  name: text('name').notNull(), // 이름 (필수)
  age: integer('age').notNull(), // 나이 (필수)
  email: text('email').unique().notNull(), // 이메일 (고유, 필수)
});

// 게시물 테이블 정의
export const postsTable = sqliteTable('posts', {
  id: integer('id').primaryKey(), // 기본 키
  title: text('title').notNull(), // 제목 (필수)
  content: text('content').notNull(), // 내용 (필수)
  userId: integer('user_id') // 사용자 ID (외래 키)
    .notNull()
    .references(() => usersTable.id, { onDelete: 'cascade' }), // 사용자 삭제 시 연쇄 삭제
  createdAt: text('created_at') // 생성일
    .default(sql`(CURRENT_TIMESTAMP)`) // 기본값: 현재 시간
    .notNull(),
  updateAt: integer('updated_at', { mode: 'timestamp' }) // 수정일
    .$onUpdate(() => new Date()), // 업데이트 시 현재 시간으로 자동 설정
});

// 사용자 삽입 타입
export type InsertUser = typeof usersTable.$inferInsert;
// 사용자 선택 타입
export type SelectUser = typeof usersTable.$inferSelect;

// 게시물 삽입 타입
export type InsertPost = typeof postsTable.$inferInsert;
// 게시물 선택 타입
export type SelectPost = typeof postsTable.$inferSelect;

Drizzle 설정 파일 설정하기

Drizzle 설정 파일Drizzle Kit에서 사용되는 파일로, 데이터베이스 연결 정보, 마이그레이션 폴더, 스키마 파일 등에 대한 모든 정보를 포함합니다.

프로젝트 루트에 drizzle.config.ts 파일을 생성하고 다음 내용을 추가하세요:

drizzle.config.ts
import { config } from 'dotenv';
import { defineConfig } from 'drizzle-kit';

// .env 파일에서 환경 변수 로드
config({ path: '.env' });

export default defineConfig({
  schema: './src/db/schema.ts', // 스키마 파일 경로
  out: './migrations',          // 마이그레이션 파일이 생성될 경로
  dialect: 'turso',             // 사용할 데이터베이스 dialect
  dbCredentials: {
    url: process.env.TURSO_CONNECTION_URL!, // Turso 연결 URL
    authToken: process.env.TURSO_AUTH_TOKEN!, // Turso 인증 토큰
  },
});

이 설정 파일은 Drizzle Kit이 데이터베이스와 상호작용하는 데 필요한 정보를 제공합니다. schema는 데이터베이스 스키마를 정의한 파일의 경로를, out은 마이그레이션 파일이 저장될 경로를 지정합니다. dialect는 사용할 데이터베이스의 종류를 나타내며, dbCredentials에는 데이터베이스 연결에 필요한 자격 증명이 포함됩니다.

데이터베이스에 변경 사항 적용하기

drizzle-kit generate 명령어를 사용해 마이그레이션을 생성하고, drizzle-kit migrate 명령어로 실행할 수 있습니다.

마이그레이션 생성:

npx drizzle-kit generate

생성된 마이그레이션은 drizzle.config.ts에 지정된 migrations 디렉토리에 저장됩니다. 이 디렉토리에는 데이터베이스 스키마를 업데이트하는 데 필요한 SQL 파일과, 다양한 마이그레이션 단계에서의 스키마 스냅샷을 저장하는 meta 폴더가 포함됩니다.

생성된 마이그레이션 예제:

CREATE TABLE `posts` (
	`id` integer PRIMARY KEY NOT NULL,
	`title` text NOT NULL,
	`content` text NOT NULL,
	`user_id` integer NOT NULL,
	`created_at` text DEFAULT (CURRENT_TIMESTAMP) NOT NULL,
	`updated_at` integer,
	FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade
);
--> statement-breakpoint
CREATE TABLE `users` (
	`id` integer PRIMARY KEY NOT NULL,
	`name` text NOT NULL,
	`age` integer NOT NULL,
	`email` text NOT NULL
);
--> statement-breakpoint
CREATE UNIQUE INDEX `users_email_unique` ON `users` (`email`);

마이그레이션 실행:

npx drizzle-kit migrate

또는, Drizzle kit push 명령어를 사용해 데이터베이스에 직접 변경 사항을 적용할 수도 있습니다:

npx drizzle-kit push
IMPORTANT

푸시 명령어는 로컬 개발 환경에서 새로운 스키마 디자인이나 변경 사항을 빠르게 테스트해야 할 때 유용합니다. 마이그레이션 파일을 관리하는 오버헤드 없이 빠르게 반복 작업을 할 수 있습니다.

기본 파일 구조

이 프로젝트의 기본 파일 구조입니다. src/db 디렉토리에는 데이터베이스 관련 파일들이 위치하며, index.ts 파일에는 데이터베이스 연결 설정이, schema.ts 파일에는 스키마 정의가 포함되어 있습니다.

📦 <project root>
 ├ 📂 src
 │   ├ 📂 db
 │   │  ├ 📜 index.ts
 │   │  └ 📜 schema.ts
 ├ 📂 migrations
 │  ├ 📂 meta
 │  │  ├ 📜 _journal.json
 │  │  └ 📜 0000_snapshot.json
 │  └ 📜 0000_watery_spencer_smythe.sql
 ├ 📜 .env
 ├ 📜 drizzle.config.ts
 ├ 📜 package.json
 └ 📜 tsconfig.json

쿼리 예제

예를 들어, src/db/queries 폴더를 만들고 각 작업(삽입, 선택, 업데이트, 삭제)별로 파일을 분리합니다.

데이터 삽입

데이터 삽입 쿼리에 대한 자세한 내용은 문서를 참고하세요.

src/db/queries/insert.ts
import { db } from '../index';
import { InsertPost, InsertUser, postsTable, usersTable } from '../schema';

export async function createUser(data: InsertUser) {
  await db.insert(usersTable).values(data);
}

export async function createPost(data: InsertPost) {
  await db.insert(postsTable).values(data);
}

데이터 선택하기

선택 쿼리에 대해 더 알아보려면 문서를 참고하세요.

src/db/queries/select.ts
import { asc, count, eq, getTableColumns, gt, sql } from 'drizzle-orm';
import { db } from '../index';
import { SelectUser, postsTable, usersTable } from '../schema';

export async function getUserById(id: SelectUser['id']): Promise<
  Array<{
    id: number;
    name: string;
    age: number;
    email: string;
  }>
> {
  return db.select().from(usersTable).where(eq(usersTable.id, id));
}

export async function getUsersWithPostsCount(
  page = 1,
  pageSize = 5,
): Promise<
  Array<{
    postsCount: number;
    id: number;
    name: string;
    age: number;
    email: string;
  }>
> {
  return db
    .select({
      ...getTableColumns(usersTable),
      postsCount: count(postsTable.id),
    })
    .from(usersTable)
    .leftJoin(postsTable, eq(usersTable.id, postsTable.userId))
    .groupBy(usersTable.id)
    .orderBy(asc(usersTable.id))
    .limit(pageSize)
    .offset((page - 1) * pageSize);
}

export async function getPostsForLast24Hours(
  page = 1,
  pageSize = 5,
): Promise<
  Array<{
    id: number;
    title: string;
  }>
> {
  return db
    .select({
      id: postsTable.id,
      title: postsTable.title,
    })
    .from(postsTable)
    .where(gt(postsTable.createdAt, sql`(datetime('now','-24 hour'))`))
    .orderBy(asc(postsTable.title), asc(postsTable.id))
    .limit(pageSize)
    .offset((page - 1) * pageSize);
}

또는 관계형 쿼리 구문을 사용할 수도 있습니다.

데이터 업데이트

데이터 업데이트 쿼리에 대해 더 알아보려면 문서를 참고하세요.

src/db/queries/update.ts
import { eq } from 'drizzle-orm';
import { db } from '../index';
import { SelectPost, postsTable } from '../schema';

export async function updatePost(id: SelectPost['id'], data: Partial<SelectPost>) {
  await db.update(postsTable).set(data).where(eq(postsTable.id, id));
}

데이터 삭제

삭제 쿼리에 대한 자세한 내용은 문서에서 확인할 수 있습니다.

src/db/queries/delete.ts
import { eq } from 'drizzle-orm';
import { db } from '../index';
import { SelectUser, usersTable } from '../schema';

export async function deleteUser(id: SelectUser['id']) {
  await db.delete(usersTable).where(eq(usersTable.id, id));
}