Drizzle with Turso

이 튜토리얼은 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가 설치되어 있어야 합니다. 자세한 정보는 공식 문서를 확인하세요

Turso는 SQLite의 오픈 컨트리뷰션 포크인 libSQL을 기반으로 구축된 SQLite 호환 데이터베이스입니다. 조직당 수십만 개의 데이터베이스로 확장할 수 있으며, 자체 서버를 포함한 모든 위치로의 복제를 지원하여 마이크로초 단위의 지연 시간으로 액세스할 수 있습니다. Turso의 개념에 대한 자세한 내용은 여기에서 확인할 수 있습니다.

Drizzle ORM은 libSQL 드라이버를 기본적으로 지원합니다. 우리는 SQL 방언과 방언별 드라이버 및 구문을 채택하며, 가장 인기 있는 SQLite와 유사한 all, get, values, run 쿼리 메서드 구문을 반영합니다.

Turso 데이터베이스 설정은 공식 문서를 확인하세요.

Turso와 Drizzle ORM 설정

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 파일을 생성하고 데이터베이스 설정을 구성하세요:

src/db/index.ts
import { config } from 'dotenv';
import { drizzle } from 'drizzle-orm/libsql';

config({ path: '.env' }); // or .env.local

export const db = drizzle({ connection: {
  url: process.env.TURSO_CONNECTION_URL!,
  authToken: process.env.TURSO_AUTH_TOKEN!,
}});

테이블 생성

src/db 디렉토리에 schema.ts 파일을 생성하고 테이블을 정의하세요:

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')
    .notNull()
    .references(() => usersTable.id, { onDelete: 'cascade' }),
  createdAt: text('created_at')
    .default(sql`(CURRENT_TIMESTAMP)`)
    .notNull(),
  updatedAt: 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 config - Drizzle Kit에서 사용되며, 데이터베이스 연결, 마이그레이션 폴더, 스키마 파일에 대한 모든 정보를 포함하는 설정 파일입니다.

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

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

config({ path: '.env' });

export default defineConfig({
  schema: './src/db/schema.ts',
  out: './migrations',
  dialect: 'turso',
  dbCredentials: {
    url: process.env.TURSO_CONNECTION_URL!,
    authToken: process.env.TURSO_AUTH_TOKEN!,
  },
});

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

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
Push 명령은 로컬 개발 환경에서 새로운 스키마 디자인이나 변경사항을 빠르게 테스트해야 하는 상황에 적합하며, 마이그레이션 파일을 관리하는 오버헤드 없이 빠른 반복을 가능하게 합니다.

기본 파일 구조

프로젝트의 기본 파일 구조입니다. 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);
}

데이터 조회

조회 쿼리에 대한 자세한 내용은 문서를 참조하세요.

IMPORTANT

getColumnsdrizzle-orm@1.0.0-beta.2부터 사용 가능합니다(자세히 보기)

버전 1 이전(0.45.1과 같은)을 사용하는 경우 getTableColumns를 사용하세요

src/db/queries/select.ts
import { asc, count, eq, getColumns, 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({
      ...getColumns(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<Omit<SelectPost, 'id'>>) {
  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));
}