Next.js App Router에서 Drizzle ORM으로 데이터베이스 통합하기

Next.js App Router와 Drizzle ORM, Neon PostgreSQL로 풀스택 앱을 구축하는 실전 가이드. 스키마 정의부터 Server Components 조회, Server Actions CRUD, 페이지네이션, Edge Runtime 배포까지 코드와 함께 다룹니다.

왜 Drizzle ORM인가 — Next.js 풀스택 개발자들이 주목하는 이유

Next.js App Router로 풀스택 앱을 만들다 보면, 결국 데이터베이스 연동이라는 벽에 부딪힙니다. 그리고 2026년 현재, 그 벽을 넘는 도구로 Drizzle ORM이 빠르게 자리잡고 있어요.

이유가 뭘까요? 솔직히 꽤 매력적입니다.

번들 크기가 약 7.4KB(minified+gzip)에 불과하고, 런타임 의존성이 0개입니다. TypeScript 스키마에서 타입이 즉시 추론되기 때문에 Prisma처럼 별도의 generate 단계도 필요 없죠. 그리고 무엇보다 — Edge Runtime과 서버리스 환경에서 추가 설정 없이 바로 동작합니다.

이 글에서는 Next.js App Router + Drizzle ORM + Neon PostgreSQL 조합으로 프로덕션 수준의 풀스택 앱을 구축하는 과정을 처음부터 끝까지 다뤄봅니다. 스키마 정의, 마이그레이션, Server Components 데이터 조회, Server Actions CRUD, 페이지네이션, Edge Runtime 배포까지 전부요. 자, 바로 시작해 볼까요?

프로젝트 초기 설정

Next.js 프로젝트 생성

먼저 Next.js 프로젝트부터 만들어야겠죠. App Router와 TypeScript를 사용합니다.

npx create-next-app@latest my-drizzle-app --typescript --app
cd my-drizzle-app

Drizzle ORM 및 의존성 설치

Drizzle ORM 코어 패키지와 Neon PostgreSQL 서버리스 드라이버, 그리고 개발 도구인 Drizzle Kit을 설치합니다.

# Drizzle ORM 코어 + Neon 서버리스 드라이버
npm install drizzle-orm @neondatabase/serverless

# 개발 도구 (마이그레이션, 스키마 관리)
npm install -D drizzle-kit

로컬에서 일반 PostgreSQL을 쓰고 있다면 pg 패키지도 추가해 주세요.

npm install pg
npm install -D @types/pg

Neon PostgreSQL 데이터베이스 생성

Neon은 서버리스 PostgreSQL 플랫폼인데, 무료 티어로 시작할 수 있어서 사이드 프로젝트에 딱입니다. Neon 콘솔에서 프로젝트를 만들면 연결 문자열이 나옵니다. 이걸 .env 파일에 넣으면 됩니다.

# .env
DATABASE_URL="postgresql://user:[email protected]/neondb?sslmode=require"

Drizzle 설정 파일 구성

프로젝트 루트에 drizzle.config.ts를 만들어야 합니다. Drizzle Kit이 스키마 위치, 마이그레이션 출력 경로, DB 연결 정보를 파악하는 데 이 파일을 사용해요.

// drizzle.config.ts
import { defineConfig } from "drizzle-kit";

export default defineConfig({
  strict: true,
  verbose: true,
  out: "./drizzle",
  dialect: "postgresql",
  schema: "./src/db/schema.ts",
  dbCredentials: {
    url: process.env.DATABASE_URL!,
  },
});

각 옵션이 뭘 하는지 간단히 정리하면요.

  • strict: 마이그레이션 생성 시 확인 프롬프트를 보여줍니다
  • verbose: 실행 과정을 상세하게 출력합니다
  • out: SQL 마이그레이션 파일이 생성되는 디렉토리
  • dialect: 사용할 데이터베이스 종류
  • schema: TypeScript 스키마 파일 경로

데이터베이스 연결 설정

src/db/index.ts를 만들어서 DB 연결 인스턴스를 세팅합니다. 여기서 핵심은 개발 환경의 핫 리로드 때 연결 풀이 중복으로 만들어지는 걸 방지하는 겁니다.

// src/db/index.ts
import { neon } from "@neondatabase/serverless";
import { drizzle } from "drizzle-orm/neon-http";
import * as schema from "./schema";

const sql = neon(process.env.DATABASE_URL!);

export const db = drizzle(sql, { schema });

여기서 schema를 두 번째 인자로 넘기는 이유가 있습니다. 이렇게 해야 Drizzle의 Relational Query API(즉 db.query)를 쓸 수 있거든요. 일반 쿼리 빌더(db.select())만 쓸 거면 생략해도 되지만, 솔직히 관계형 데이터를 중첩 객체로 가져오는 기능은 실무에서 정말 유용하니까 처음부터 넣어두는 걸 추천합니다.

로컬 PostgreSQL을 사용하는 개발 환경이라면 이렇게 구성하세요.

// src/db/index.ts (로컬 개발용)
import { Pool } from "pg";
import { drizzle } from "drizzle-orm/node-postgres";
import * as schema from "./schema";

const pool = new Pool({
  connectionString: process.env.DATABASE_URL!,
});

export const db = drizzle(pool, { schema });

TypeScript로 스키마 정의하기

개인적으로 Drizzle ORM에서 가장 마음에 드는 부분이 바로 이겁니다. 스키마를 TypeScript 코드로 직접 정의할 수 있다는 점이요. 별도의 DSL이나 스키마 파일 없이, 순수 TypeScript로 테이블과 관계를 선언합니다.

기본 테이블 정의

블로그 앱을 예시로 스키마를 만들어 보겠습니다.

// src/db/schema.ts
import {
  pgTable,
  serial,
  text,
  timestamp,
  integer,
  boolean,
  varchar,
} from "drizzle-orm/pg-core";
import { relations } from "drizzle-orm";

// 사용자 테이블
export const users = pgTable("users", {
  id: serial("id").primaryKey(),
  name: varchar("name", { length: 100 }).notNull(),
  email: varchar("email", { length: 255 }).unique().notNull(),
  avatarUrl: text("avatar_url"),
  createdAt: timestamp("created_at").defaultNow().notNull(),
});

// 게시글 테이블
export const posts = pgTable("posts", {
  id: serial("id").primaryKey(),
  title: varchar("title", { length: 255 }).notNull(),
  content: text("content").notNull(),
  published: boolean("published").default(false).notNull(),
  authorId: integer("author_id")
    .references(() => users.id, { onDelete: "cascade" })
    .notNull(),
  createdAt: timestamp("created_at").defaultNow().notNull(),
  updatedAt: timestamp("updated_at").defaultNow().notNull(),
});

// 댓글 테이블
export const comments = pgTable("comments", {
  id: serial("id").primaryKey(),
  content: text("content").notNull(),
  postId: integer("post_id")
    .references(() => posts.id, { onDelete: "cascade" })
    .notNull(),
  authorId: integer("author_id")
    .references(() => users.id, { onDelete: "cascade" })
    .notNull(),
  createdAt: timestamp("created_at").defaultNow().notNull(),
});

관계(Relations) 정의

Drizzle의 relations() 함수로 테이블 간 관계를 선언해 줍니다. 이건 SQL 레벨의 외래 키와는 다른 개념으로, Relational Query API에서 중첩 데이터를 가져오기 위한 선언입니다.

// src/db/schema.ts (이어서)

export const usersRelations = relations(users, ({ many }) => ({
  posts: many(posts),
  comments: many(comments),
}));

export const postsRelations = relations(posts, ({ one, many }) => ({
  author: one(users, {
    fields: [posts.authorId],
    references: [users.id],
  }),
  comments: many(comments),
}));

export const commentsRelations = relations(comments, ({ one }) => ({
  post: one(posts, {
    fields: [comments.postId],
    references: [posts.id],
  }),
  author: one(users, {
    fields: [comments.authorId],
    references: [users.id],
  }),
}));

타입 추출

스키마에서 TypeScript 타입을 자동으로 뽑아낼 수 있는데, $inferSelect$inferInsert를 쓰면 됩니다.

// src/db/schema.ts (이어서)

// SELECT 시 반환되는 타입
export type User = typeof users.$inferSelect;
export type Post = typeof posts.$inferSelect;
export type Comment = typeof comments.$inferSelect;

// INSERT 시 사용하는 타입
export type NewUser = typeof users.$inferInsert;
export type NewPost = typeof posts.$inferInsert;
export type NewComment = typeof comments.$inferInsert;

Prisma에서는 prisma generate를 실행해야 타입이 갱신되지만, Drizzle은 스키마 파일을 수정하는 그 순간 바로 타입에 반영됩니다. 큰 프로젝트에서 이 차이가 체감이 꽤 큽니다. generate 명령어 실행하고 기다리는 시간이 없으니까요.

마이그레이션 실행

스키마를 다 정의했으면, 이제 실제 데이터베이스에 테이블을 만들 차례입니다.

# SQL 마이그레이션 파일 생성
npx drizzle-kit generate

# 마이그레이션 실행 (데이터베이스에 적용)
npx drizzle-kit migrate

generate./drizzle 디렉토리에 SQL 파일을 만들어 주고, migrate가 그 SQL을 실제 DB에 실행합니다.

개발 중에 빠르게 스키마를 적용하고 싶을 때는 push도 있습니다.

# 마이그레이션 파일 없이 직접 데이터베이스에 스키마 반영
npx drizzle-kit push

push는 프로토타이핑할 때 정말 편한데, 프로덕션에서는 꼭 generate + migrate 조합을 사용하세요. 마이그레이션 이력을 추적할 수 있어야 나중에 문제가 생겼을 때 대응이 가능하니까요.

Server Components에서 데이터 조회하기

자, 이제 진짜 재미있는 부분입니다. Next.js App Router의 Server Components에서 Drizzle로 데이터를 직접 가져와 봅시다.

기본 쿼리

// app/posts/page.tsx
import { db } from "@/db";
import { posts, users } from "@/db/schema";
import { desc, eq } from "drizzle-orm";

export default async function PostsPage() {
  // 공개된 게시글을 최신순으로 조회
  const publishedPosts = await db
    .select({
      id: posts.id,
      title: posts.title,
      createdAt: posts.createdAt,
      authorName: users.name,
    })
    .from(posts)
    .innerJoin(users, eq(posts.authorId, users.id))
    .where(eq(posts.published, true))
    .orderBy(desc(posts.createdAt));

  return (
    <main>
      <h1>게시글 목록</h1>
      <ul>
        {publishedPosts.map((post) => (
          <li key={post.id}>
            <h2>{post.title}</h2>
            <p>{post.authorName} · {post.createdAt.toLocaleDateString("ko-KR")}</p>
          </li>
        ))}
      </ul>
    </main>
  );
}

Server Component라서 async/await를 바로 쓸 수 있고, 데이터베이스 연결 정보가 클라이언트에 절대 노출되지 않습니다. 이게 바로 React Server Components가 주는 가장 큰 이점이에요.

Relational Query API로 중첩 데이터 가져오기

아까 relations()을 정의해 뒀잖아요? 그걸 활용하면 db.query API로 관계형 데이터를 중첩 객체 형태로 깔끔하게 가져올 수 있습니다. JOIN문을 직접 작성할 필요가 없어요.

// app/posts/[id]/page.tsx
import { db } from "@/db";
import { eq } from "drizzle-orm";
import { notFound } from "next/navigation";

export default async function PostDetailPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;

  const post = await db.query.posts.findFirst({
    where: (posts, { eq }) => eq(posts.id, parseInt(id)),
    with: {
      author: true,
      comments: {
        with: {
          author: true,
        },
        orderBy: (comments, { desc }) => [desc(comments.createdAt)],
      },
    },
  });

  if (!post) notFound();

  return (
    <article>
      <h1>{post.title}</h1>
      <p>작성자: {post.author.name}</p>
      <div>{post.content}</div>

      <section>
        <h2>댓글 ({post.comments.length})</h2>
        {post.comments.map((comment) => (
          <div key={comment.id}>
            <strong>{comment.author.name}</strong>
            <p>{comment.content}</p>
          </div>
        ))}
      </section>
    </article>
  );
}

with 옵션 하나로 게시글, 작성자, 댓글, 댓글 작성자까지 한 번에 가져옵니다. Drizzle이 내부적으로 최적화된 단일 SQL 쿼리를 생성하기 때문에 N+1 문제가 발생하지 않아요. 이건 진짜 깔끔합니다.

Server Actions로 CRUD 뮤테이션 구현

데이터 변경 작업(생성, 수정, 삭제)은 Server Actions를 통해 처리합니다.

Zod를 활용한 유효성 검사 스키마

먼저 입력 데이터 검증용 스키마를 정의합니다. 실무에서 유효성 검사를 빼먹으면 나중에 고통받으니까 처음부터 넣어두는 게 좋아요.

// src/lib/validations.ts
import { z } from "zod";

export const createPostSchema = z.object({
  title: z
    .string()
    .min(1, "제목을 입력하세요")
    .max(255, "제목은 255자 이내로 입력하세요"),
  content: z
    .string()
    .min(1, "내용을 입력하세요"),
  published: z.boolean().default(false),
});

export const updatePostSchema = createPostSchema.partial().extend({
  id: z.number(),
});

Server Actions 정의

// src/lib/actions.ts
"use server";

import { db } from "@/db";
import { posts } from "@/db/schema";
import { eq } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { createPostSchema, updatePostSchema } from "./validations";

// 게시글 생성
export async function createPost(formData: FormData) {
  const rawData = {
    title: formData.get("title") as string,
    content: formData.get("content") as string,
    published: formData.get("published") === "on",
  };

  const validated = createPostSchema.safeParse(rawData);
  if (!validated.success) {
    return { error: validated.error.flatten().fieldErrors };
  }

  await db.insert(posts).values({
    ...validated.data,
    authorId: 1, // 실제로는 인증된 사용자 ID를 사용
  });

  revalidatePath("/posts");
  return { success: true };
}

// 게시글 수정
export async function updatePost(formData: FormData) {
  const rawData = {
    id: Number(formData.get("id")),
    title: formData.get("title") as string,
    content: formData.get("content") as string,
    published: formData.get("published") === "on",
  };

  const validated = updatePostSchema.safeParse(rawData);
  if (!validated.success) {
    return { error: validated.error.flatten().fieldErrors };
  }

  const { id, ...data } = validated.data;
  await db
    .update(posts)
    .set({ ...data, updatedAt: new Date() })
    .where(eq(posts.id, id));

  revalidatePath("/posts");
  revalidatePath(`/posts/${id}`);
  return { success: true };
}

// 게시글 삭제
export async function deletePost(id: number) {
  await db.delete(posts).where(eq(posts.id, id));
  revalidatePath("/posts");
  return { success: true };
}

Server Actions 쓸 때 꼭 기억해야 할 원칙들이 있어요.

  • 항상 입력값을 검증하세요 — Server Actions는 사실상 공개 API 엔드포인트와 같습니다. 누구든 호출할 수 있다고 생각하고 만들어야 해요
  • 인증/인가 확인 — 현재 사용자가 그 작업을 할 권한이 있는지 반드시 체크하세요
  • revalidatePath 호출 — 데이터가 바뀌면 관련 경로 캐시를 무효화해야 합니다

클라이언트 폼에서 Server Action 호출

// app/posts/new/page.tsx
"use client";

import { createPost } from "@/lib/actions";
import { useActionState } from "react";

const initialState = { error: null, success: false };

export default function NewPostPage() {
  const [state, formAction, isPending] = useActionState(
    async (_prev: typeof initialState, formData: FormData) => {
      const result = await createPost(formData);
      return result as typeof initialState;
    },
    initialState
  );

  return (
    <form action={formAction}>
      <div>
        <label htmlFor="title">제목</label>
        <input
          id="title"
          name="title"
          type="text"
          required
        />
        {state.error?.title && (
          <p className="error">{state.error.title}</p>
        )}
      </div>

      <div>
        <label htmlFor="content">내용</label>
        <textarea id="content" name="content" required />
        {state.error?.content && (
          <p className="error">{state.error.content}</p>
        )}
      </div>

      <div>
        <label>
          <input type="checkbox" name="published" />
          바로 공개
        </label>
      </div>

      <button type="submit" disabled={isPending}>
        {isPending ? "저장 중..." : "게시글 작성"}
      </button>
    </form>
  );
}

서버 사이드 페이지네이션 구현

실무에서 빠지지 않는 기능이죠. 서버 사이드 페이지네이션을 URL 쿼리 파라미터와 Server Components를 활용해서 구현해 봅시다.

// src/lib/queries.ts
import { db } from "@/db";
import { posts, users } from "@/db/schema";
import { desc, eq, count, sql } from "drizzle-orm";

const POSTS_PER_PAGE = 10;

export async function getPaginatedPosts(page: number) {
  const offset = (page - 1) * POSTS_PER_PAGE;

  const [data, totalCount] = await Promise.all([
    db
      .select({
        id: posts.id,
        title: posts.title,
        createdAt: posts.createdAt,
        authorName: users.name,
      })
      .from(posts)
      .innerJoin(users, eq(posts.authorId, users.id))
      .where(eq(posts.published, true))
      .orderBy(desc(posts.createdAt))
      .limit(POSTS_PER_PAGE)
      .offset(offset),

    db
      .select({ count: count() })
      .from(posts)
      .where(eq(posts.published, true)),
  ]);

  return {
    posts: data,
    totalPages: Math.ceil(totalCount[0].count / POSTS_PER_PAGE),
    currentPage: page,
  };
}
// app/posts/page.tsx
import { Suspense } from "react";
import { getPaginatedPosts } from "@/lib/queries";
import Link from "next/link";

function PostListSkeleton() {
  return (
    <div>
      {Array.from({ length: 10 }).map((_, i) => (
        <div key={i} className="animate-pulse h-20 bg-gray-200 rounded mb-4" />
      ))}
    </div>
  );
}

async function PostList({ page }: { page: number }) {
  const { posts, totalPages, currentPage } = await getPaginatedPosts(page);

  return (
    <>
      <ul>
        {posts.map((post) => (
          <li key={post.id}>
            <Link href={`/posts/${post.id}`}>
              <h2>{post.title}</h2>
            </Link>
            <p>{post.authorName} · {post.createdAt.toLocaleDateString("ko-KR")}</p>
          </li>
        ))}
      </ul>

      <nav>
        {currentPage > 1 && (
          <Link href={`/posts?page=${currentPage - 1}`}>이전</Link>
        )}
        <span>{currentPage} / {totalPages}</span>
        {currentPage < totalPages && (
          <Link href={`/posts?page=${currentPage + 1}`}>다음</Link>
        )}
      </nav>
    </>
  );
}

export default async function PostsPage({
  searchParams,
}: {
  searchParams: Promise<{ page?: string }>;
}) {
  const { page } = await searchParams;
  const currentPage = Math.max(1, Number(page) || 1);

  return (
    <main>
      <h1>게시글</h1>
      <Suspense fallback={<PostListSkeleton />}>
        <PostList page={currentPage} />
      </Suspense>
    </main>
  );
}

Suspense로 감싸 놓으면 페이지 헤더나 네비게이션은 즉시 렌더링되고, 게시글 목록만 데이터가 준비되는 대로 스트리밍됩니다. 사용자 입장에서 빈 화면 대신 스켈레톤을 보게 되니 체감 속도가 확실히 다릅니다.

Edge Runtime에서 Drizzle 사용하기

Drizzle ORM이 다른 ORM과 확실하게 차별화되는 지점이 바로 Edge Runtime 네이티브 지원입니다.

비교해 보면 차이가 확연해요. Prisma는 Rust로 만든 쿼리 엔진 바이너리(약 2MB 이상)를 포함하기 때문에 Edge에서 쓰려면 별도의 Prisma Accelerate 서비스가 필요합니다. 반면 Drizzle은 그냥 됩니다. 추가 설정이 없어요.

// app/api/posts/route.ts
import { db } from "@/db";
import { posts } from "@/db/schema";
import { desc, eq } from "drizzle-orm";

// Edge Runtime으로 실행
export const runtime = "edge";

export async function GET() {
  const data = await db
    .select()
    .from(posts)
    .where(eq(posts.published, true))
    .orderBy(desc(posts.createdAt))
    .limit(20);

  return Response.json(data);
}

Edge Runtime에서 얻을 수 있는 성능 이점을 구체적으로 보면요.

  • 콜드 스타트: Prisma 대비 300~500ms 단축됩니다 (바이너리 엔진 로딩이 없으니까요)
  • TTFB: CDN 엣지에서 실행되므로 사용자에게 물리적으로 가까운 위치에서 응답합니다
  • 번들 크기: ~7.4KB vs Prisma의 2MB+ — 서버리스에서 이 차이는 꽤 결정적입니다

Drizzle Studio로 데이터 시각화

Drizzle Kit에 Drizzle Studio라는 데이터 브라우저가 내장되어 있는데, 이건 좀 숨겨진 보석 같은 기능입니다. 별도 설치가 필요 없어요.

npx drizzle-kit studio

브라우저에서 https://local.drizzle.studio로 접속하면 DB의 모든 테이블을 시각적으로 보고 데이터를 편집할 수 있습니다. pgAdmin이나 TablePlus를 따로 설치하지 않아도 되니 개발 중에 정말 편합니다.

프로덕션 배포 체크리스트

Vercel에 배포하기 전에 확인해야 할 것들을 정리해 봤습니다.

1. 환경 변수 설정

Vercel 대시보드에서 DATABASE_URL을 설정하세요. Neon을 사용한다면 풀링(Pooling) 연결 문자열을 써야 합니다. Neon 콘솔 Connection Details에서 Pooling 모드 URL을 복사하면 됩니다.

2. 마이그레이션 자동화

package.json의 빌드 스크립트에 마이그레이션을 넣어두면 배포할 때마다 자동으로 실행됩니다.

{
  "scripts": {
    "build": "drizzle-kit migrate && next build",
    "db:generate": "drizzle-kit generate",
    "db:migrate": "drizzle-kit migrate",
    "db:push": "drizzle-kit push",
    "db:studio": "drizzle-kit studio"
  }
}

3. 연결 풀링 최적화

서버리스 환경에서는 매 요청마다 새 함수 인스턴스가 생길 수 있어서, DB 연결 수가 급격히 늘어날 수 있습니다. 다행히 Neon에는 내장 커넥션 풀러가 있으니 풀링 엔드포인트를 사용하면 이 문제를 해결할 수 있습니다.

Prisma에서 Drizzle로 마이그레이션하기

이미 Prisma를 쓰고 있는 프로젝트라면, 한 번에 바꾸지 말고 단계적으로 전환하는 걸 추천합니다.

  1. Drizzle 설치 및 스키마 변환 — 기존 Prisma 스키마를 Drizzle의 TypeScript 스키마로 변환합니다
  2. 새로운 기능부터 Drizzle 사용 — 기존 코드는 Prisma를 유지하면서 신규 기능에서만 Drizzle을 도입합니다
  3. 점진적 교체 — 시간을 두고 Prisma 쿼리를 하나씩 Drizzle로 바꿔 나갑니다
  4. Prisma 제거 — 모든 쿼리가 전환되면 그때 Prisma를 걷어냅니다

핵심은 빅뱅 마이그레이션을 피하는 것입니다. 두 ORM이 한동안 공존해도 괜찮아요. 안정성이 더 중요하니까요.

자주 묻는 질문 (FAQ)

Drizzle ORM과 Prisma 중 어떤 것을 선택해야 하나요?

팀의 SQL 경험에 따라 다릅니다. SQL에 익숙한 백엔드 개발자 중심 팀이라면 Drizzle이 자연스럽게 느껴질 거예요. 반면 프론트엔드 중심이거나 Rails/Django 경험이 있는 팀이라면 Prisma의 추상화가 적응하기 더 쉬울 수 있습니다. 다만 번들 크기와 성능이 중요한 서버리스/엣지 환경이라면 Drizzle이 확실히 유리합니다.

Drizzle ORM은 프로덕션에서 안정적인가요?

네, 충분히 안정적입니다. GitHub 스타 25,000개 이상을 보유하고 있고, 실제 프로덕션 환경에서 많이 사용되고 있습니다. 2026년 현재 v1.0.0 베타가 진행 중이지만, 기존 안정 버전(0.45.x)으로도 프로덕션에 무리가 없어요.

Next.js에서 Drizzle ORM의 연결 풀링은 어떻게 관리하나요?

서버리스 환경에서는 Neon이나 Supabase 같은 서버리스 DB의 내장 커넥션 풀러를 사용하는 게 가장 깔끔합니다. Neon의 HTTP 기반 서버리스 드라이버(@neondatabase/serverless)를 쓰면 커넥션 관리를 DB 쪽에서 해주기 때문에 따로 풀링을 설정할 필요가 없어요. 로컬에서는 pgPool 클래스를 쓰면 됩니다.

Drizzle ORM에서 트랜잭션은 어떻게 사용하나요?

db.transaction() 메서드를 쓰면 됩니다. 콜백 안에서 여러 쿼리를 실행하면 전부 하나의 트랜잭션으로 묶이고, 에러가 나면 자동 롤백됩니다. Server Actions에서 여러 테이블을 동시에 수정해야 할 때 특히 유용해요.

Drizzle ORM은 MongoDB를 지원하나요?

아니요, Drizzle은 SQL 데이터베이스 전용입니다. PostgreSQL, MySQL, SQLite, MSSQL을 지원하고, Turso, Neon, PlanetScale, Cloudflare D1 같은 서버리스 SQL DB도 지원합니다. MongoDB가 필요하다면 Prisma나 Mongoose를 사용해야 합니다.

저자 소개 Editorial Team

Our team of expert writers and editors.