설치 중 의존성 문제가 발생할 경우:
React Native를 사용하지 않는다면, --force
나 --legacy-peer-deps
옵션을 사용해 강제로 설치하면 문제가 해결될 수 있습니다. React Native를 사용한다면, React Native 버전과 호환되는 정확한 React 버전을 사용해야 합니다.
이 튜토리얼은 Drizzle ORM과 Neon 데이터베이스, Next.js를 사용해 Todo 앱
을 만드는 방법을 보여줍니다.
npx create-next-app@latest --typescript
npm i drizzle-orm
npm i -D drizzle-kit
npm i @neondatabase/serverless
dotenv
패키지를 설치해야 합니다.npm i dotenv
설치 중 의존성 문제가 발생할 경우:
React Native를 사용하지 않는다면, --force
나 --legacy-peer-deps
옵션을 사용해 강제로 설치하면 문제가 해결될 수 있습니다. React Native를 사용한다면, React Native 버전과 호환되는 정확한 React 버전을 사용해야 합니다.
Neon Console에 로그인한 후, Projects 섹션으로 이동합니다. 기존 프로젝트를 선택하거나 New Project
버튼을 클릭해 새 프로젝트를 생성합니다.
Neon 프로젝트는 neondb
라는 이름의 Postgres 데이터베이스를 기본으로 제공합니다. 이 튜토리얼에서는 이 데이터베이스를 사용할 예정입니다.
프로젝트 콘솔에서 Connection Details 섹션으로 이동하여 데이터베이스 연결 문자열을 찾습니다. 다음과 비슷한 형태일 것입니다:
postgres://username:password@ep-cool-darkness-123456.us-east-2.aws.neon.tech/neondb
Neon 데이터베이스에 연결할 때 사용할 DATABASE_URL
환경 변수를 .env
또는 .env.local
파일에 추가합니다.
DATABASE_URL=NEON_DATABASE_CONNECTION_STRING
src/db
폴더 안에 drizzle.ts
파일을 생성하고 데이터베이스 설정을 구성합니다:
import { config } from "dotenv";
import { drizzle } from 'drizzle-orm/neon-http';
// .env 또는 .env.local 파일에서 환경 변수를 로드
config({ path: ".env" });
// Drizzle ORM을 사용해 데이터베이스 연결 설정
export const db = drizzle(process.env.DATABASE_URL!);
이 코드는 .env
파일에서 환경 변수를 읽어와 Drizzle ORM을 통해 데이터베이스에 연결합니다. process.env.DATABASE_URL
은 데이터베이스 연결 정보를 담고 있는 환경 변수입니다.
import { integer, text, boolean, pgTable } from "drizzle-orm/pg-core";
export const todo = pgTable("todo", {
id: integer("id").primaryKey(),
text: text("text").notNull(),
done: boolean("done").default(false).notNull(),
});
여기서는 Drizzle ORM의 데이터 타입을 사용하여 todo
테이블을 정의합니다. 이 테이블에는 id
, text
, done
필드가 포함되어 있습니다.
Drizzle 설정 파일은 Drizzle Kit에서 사용하는 구성 파일로, 데이터베이스 연결 정보, 마이그레이션 폴더, 스키마 파일 등에 대한 모든 정보를 포함합니다.
프로젝트 루트에 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: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL!,
},
});
drizzle-kit generate
명령어를 사용해 마이그레이션을 생성하고, drizzle-kit migrate
명령어로 실행할 수 있습니다.
마이그레이션 생성하기:
npx drizzle-kit generate
생성된 마이그레이션은 drizzle.config.ts
에 지정된 drizzle/migrations
디렉토리에 저장됩니다. 이 디렉토리에는 데이터베이스 스키마를 업데이트하는 데 필요한 SQL 파일과, 다양한 마이그레이션 단계에서의 스키마 스냅샷을 저장하는 meta
폴더가 포함됩니다.
생성된 마이그레이션 예시:
CREATE TABLE IF NOT EXISTS "todo" (
"id" integer PRIMARY KEY NOT NULL,
"text" text NOT NULL,
"done" boolean DEFAULT false NOT NULL
);
마이그레이션 실행하기:
npx drizzle-kit migrate
또는, Drizzle kit push 명령어를 사용해 데이터베이스에 직접 변경 사항을 적용할 수도 있습니다:
npx drizzle-kit push
push
명령어는 로컬 개발 환경에서 새로운 스키마 디자인이나 변경 사항을 빠르게 테스트해야 할 때 유용합니다. 마이그레이션 파일을 관리하는 오버헤드 없이 빠르게 반복 작업을 할 수 있습니다.
이 단계에서는 src/actions/todoAction.ts 파일에 서버 사이드 함수를 설정하여 할 일 항목에 대한 주요 작업을 처리합니다.
getData
:
addTodo
:
revalidatePath("/")
**를 사용하여 홈 페이지의 재검증을 시작합니다.deleteTodo
:
toggleTodo
:
editTodo
:
"use server";
import { eq, not } from "drizzle-orm";
import { revalidatePath } from "next/cache";
import { db } from "@/db/drizzle";
import { todo } from "@/db/schema";
export const getData = async () => {
const data = await db.select().from(todo);
return data;
};
export const addTodo = async (id: number, text: string) => {
await db.insert(todo).values({
id: id,
text: text,
});
};
export const deleteTodo = async (id: number) => {
await db.delete(todo).where(eq(todo.id, id));
revalidatePath("/");
};
export const toggleTodo = async (id: number) => {
await db
.update(todo)
.set({
done: not(todo.done),
})
.where(eq(todo.id, id));
revalidatePath("/");
};
export const editTodo = async (id: number, text: string) => {
await db
.update(todo)
.set({
text: text,
})
.where(eq(todo.id, id));
revalidatePath("/");
};
프로젝트 생성
npx create-next-app@latest my-app
my-app
은 여러분이 원하는 프로젝트 이름으로 변경할 수 있습니다.프로젝트 디렉토리 이동
cd my-app
개발 서버 실행
npm run dev
http://localhost:3000
으로 접속하여 기본 홈페이지를 확인할 수 있습니다.홈페이지 수정
pages/index.js
파일을 열어 홈페이지 내용을 수정합니다. 예를 들어, 다음과 같이 간단한 내용을 추가할 수 있습니다:
export default function Home() {
return (
<div>
<h1>환영합니다!</h1>
<p>이것은 Next.js로 만든 홈페이지입니다.</p>
</div>
);
}
스타일 추가
styles/Home.module.css
파일을 생성하고 다음과 같이 스타일을 정의합니다:
.container {
padding: 0 2rem;
}
.title {
margin: 0;
line-height: 1.15;
font-size: 4rem;
}
index.js
파일에서 이 스타일을 적용합니다:
import styles from '../styles/Home.module.css';
export default function Home() {
return (
<div className={styles.container}>
<h1 className={styles.title}>환영합니다!</h1>
<p>이것은 Next.js로 만든 홈페이지입니다.</p>
</div>
);
}
변경 사항 확인
배포 준비
npm run build
npm start
이제 여러분은 Next.js를 사용하여 간단한 홈페이지를 설정하고 수정하는 방법을 알게 되었습니다. 추가적인 기능이나 페이지를 추가하려면 Next.js 공식 문서를 참고하세요.
src/types/todoType.ts
파일에서 **todoType
**이라는 이름의 타입을 정의합니다. 이 타입은 일반적인 할 일 항목의 구조를 나타내며, 세 가지 속성을 가집니다: **id
**는 number
타입, **text
**는 string
타입, 그리고 **done
**은 boolean
타입입니다.
export type todoType = {
id: number;
text: string;
done: boolean;
};
src/components/todo.tsx
:
Todo
컴포넌트를 생성합니다. 이 컴포넌트는 단일 할 일 항목을 나타내며, 할 일 텍스트를 표시하고 편집할 수 있는 기능, 체크박스를 통해 완료 상태를 표시하는 기능, 그리고 편집, 저장, 취소, 삭제 작업을 제공합니다.
src/components/addTodo.tsx
:
AddTodo
컴포넌트는 새로운 할 일 항목을 추가하기 위한 간단한 폼을 제공합니다. 할 일 텍스트를 입력할 수 있는 입력 필드와 새로운 할 일을 추가하는 버튼이 포함됩니다.
src/components/todos.tsx
:
Todos
컴포넌트는 To-do 애플리케이션의 메인 인터페이스를 나타냅니다. 할 일 항목의 상태를 관리하고, 할 일을 생성, 편집, 토글, 삭제하는 함수를 제공하며, Todo
컴포넌트를 사용하여 개별 할 일 항목을 렌더링합니다.
"use client";
import { ChangeEvent, FC, useState } from "react";
import { todoType } from "@/types/todoType";
interface Props {
todo: todoType;
changeTodoText: (id: number, text: string) => void;
toggleIsTodoDone: (id: number, done: boolean) => void;
deleteTodoItem: (id: number) => void;
}
const Todo: FC = ({
todo,
changeTodoText,
toggleIsTodoDone,
deleteTodoItem,
}) => {
// 편집 모드를 관리하는 상태
const [editing, setEditing] = useState(false);
// 텍스트 입력을 관리하는 상태
const [text, setText] = useState(todo.text);
// 완료 상태를 관리하는 상태
const [isDone, setIsDone] = useState(todo.done);
// 텍스트 입력 변경 이벤트 핸들러
const handleTextChange = (e: ChangeEvent) => {
setText(e.target.value);
};
// 완료 상태 토글 이벤트 핸들러
const handleIsDone = async () => {
toggleIsTodoDone(todo.id, !isDone);
setIsDone((prev) => !prev);
};
// 편집 모드 시작 이벤트 핸들러
const handleEdit = () => {
setEditing(true);
};
// 편집된 텍스트 저장 이벤트 핸들러
const handleSave = async () => {
changeTodoText(todo.id, text);
setEditing(false);
};
// 편집 모드 취소 이벤트 핸들러
const handleCancel = () => {
setEditing(false);
setText(todo.text);
};
// 할 일 항목 삭제 이벤트 핸들러
const handleDelete = () => {
if (confirm("이 할 일을 삭제하시겠습니까?")) {
deleteTodoItem(todo.id);
}
};
// Todo 컴포넌트 렌더링
return (
<div>
{/* 완료 상태를 표시하는 체크박스 */}
<input
type="checkbox"
className="text-blue-200 rounded-sm h-4 w-4"
checked={isDone}
onChange={handleIsDone}
/>
{/* 할 일 텍스트 입력 필드 */}
<input
type="text"
value={text}
onChange={handleTextChange}
readOnly={!editing}
className={`${
todo.done ? "line-through" : ""
} outline-none read-only:border-transparent focus:border border-gray-200 rounded px-2 py-1 w-full`}
/>
{/* 편집, 저장, 취소, 삭제 버튼 */}
<div>
{editing ? (
<button
onClick={handleSave}
className="bg-green-600 text-green-50 rounded px-2 w-14 py-1"
>
저장
</button>
) : (
<button
onClick={handleEdit}
className="bg-blue-400 text-blue-50 rounded w-14 px-2 py-1"
>
편집
</button>
)}
{editing ? (
<button
onClick={handleCancel}
className="bg-red-400 w-16 text-red-50 rounded px-2 py-1"
>
닫기
</button>
) : (
<button
onClick={handleDelete}
className="bg-red-400 w-16 text-red-50 rounded px-2 py-1"
>
삭제
</button>
)}
</div>
</div>
);
};
export default Todo;
src/app
폴더의 page.tsx
파일을 업데이트하여 데이터베이스에서 할 일 항목을 가져오고 Todos
컴포넌트를 렌더링합니다:
import { getData } from "@/actions/todoAction";
import Todos from "@/components/todos";
export default async function Home() {
const data = await getData();
return <Todos todos={data} />;
}
이 가이드에서는 다음과 같은 파일 구조를 사용합니다:
📦 <project root>
├ 📂 migrations
│ ├ 📂 meta
│ └ 📜 0000_heavy_doctor_doom.sql
├ 📂 public
├ 📂 src
│ ├ 📂 actions
│ │ └ 📜 todoActions.ts
│ ├ 📂 app
│ │ ├ 📜 favicon.ico
│ │ ├ 📜 globals.css
│ │ ├ 📜 layout.tsx
│ │ └ 📜 page.tsx
│ ├ 📂 components
│ │ ├ 📜 addTodo.tsx
│ │ ├ 📜 todo.tsx
│ │ └ 📜 todos.tsx
│ └ 📂 db
│ │ ├ 📜 drizzle.ts
│ │ └ 📜 schema.ts
│ └ 📂 types
│ └ 📜 todoType.ts
├ 📜 .env
├ 📜 .eslintrc.json
├ 📜 .gitignore
├ 📜 drizzle.config.ts
├ 📜 next-env.d.ts
├ 📜 next.config.mjs
├ 📜 package-lock.json
├ 📜 package.json
├ 📜 postcss.config.mjs
├ 📜 README.md
├ 📜 tailwind.config.ts
└ 📜 tsconfig.json