0441

FE

[React] React + Node.js 풀스택 CRUD 만들기 (Prisma + SQLite)

| 서론

안녕하세요, 팡일입니다.

이전 글에서는 React + Node.js 기반의 풀스택 모노레포 환경을 구축하는 과정을 정리했고,

이번 글에서는 그 환경 위에서 실제로 데이터를 저장하고 조회할 수 있는 CRUD 기능을 구현해보겠습니다.

전체 데이터 흐름은 다음과 같습니다.

code
React
 ↓
Express API
 ↓
Prisma
 ↓
SQLite Database

간단한 Todo API를 만들어 React에서 데이터를 저장하고 조회해보겠습니다.

| 1단계 : Prisma 설치

1) Prisma ORM

Node 서버에서 데이터베이스를 사용하기 위해 Prisma ORM을 사용합니다.

Prisma는 TypeScript 환경에서 매우 사용하기 편한 ORM입니다.

  • 타입 자동 생성

  • DB 마이그레이션 지원

  • 직관적인 API

2) 설치

: 먼저 api 패키지에 Prisma를 설치합니다.

code
pnpm add prisma@6 @prisma/client@6 --filter api

Prisma CLI도 설치합니다.

code
pnpm add -D prisma@6 --filter api

| 2단계 : Prisma 초기화

이제 Prisma 설정을 생성합니다.

code
cd apps/api
npx prisma init

생성 후에는 다음과 같은 구조가 생성됩니다.

code
apps/api
 ├ prisma
 │   └ schema.prisma
 ├ .env
 └ prisma.config.ts

| 3단계 : SQLite 데이터베이스 설정

이번 프로젝트에서는 로컬에서 간단히 테스트할 수 있는 SQLite를 사용합니다.

.env 파일을 수정합니다.

code
DATABASE_URL="file:./dev.db"

SQLite는 별도의 데이터베이스 서버를 설치하지 않아도 되기 때문에 로컬 환경에서 빠르게 CRUD 기능을 테스트할 수 있습니다.

| 4단계 : 데이터 모델 정의

schema.prisma 파일에서 Todo 모델을 정의합니다.

code
model Todo {
  id        Int      @id @default(autoincrement())
  title     String
  completed Boolean  @default(false)
  createdAt DateTime @default(now())
}

이 모델을 기준으로 데이터베이스 테이블이 생성됩니다.

| 5단계 : 데이터베이스 마이그레이션

모델을 정의했다면 실제 데이터베이스를 생성합니다.

code
npx prisma migrate dev --name init

이 명령을 실행하면 다음 작업이 수행됩니다.

  • SQLite DB 생성

  • migration 파일 생성

  • Prisma Client 생성

실행 후 다음 파일이 생성됩니다.

code
apps/api/prisma/dev.db

| 6단계 : Prisma Client 생성

Prisma Client는 Node.js에서 데이터베이스를 제어하는 역할을 합니다.

1) 생성

: 아래의 명령어로 Prisma Client를 사용할 파일을 생성합니다.

code
npx prisma generate

2) 작성

: src/prisma.ts 파일에 아래의 내용을 작성합니다.

JavaScript
import { PrismaClient } from "@prisma/client";

export const prisma = new PrismaClient();

이제 Express 서버에서 Prisma를 통해 데이터베이스에 접근할 수 있습니다.

| 7단계 : Todo CRUD API 구현

이제 Express 서버에 CRUD API를 구현합니다.

1) src/index.ts

TypeScript
import express from "express";
import cors from "cors";
import { prisma } from "./prisma";

const app = express();

app.use(cors());
app.use(express.json());

app.get("/api/todos", async (req, res) => {
  const todos = await prisma.todo.findMany();
  res.json(todos);
});

app.post("/api/todos", async (req, res) => {
  const { title } = req.body;

  const todo = await prisma.todo.create({
    data: { title },
  });

  res.json(todo);
});

app.patch("/api/todos/:id", async (req, res) => {
  const id = Number(req.params.id);

  const todo = await prisma.todo.update({
    where: { id },
    data: { completed: true },
  });

  res.json(todo);
});

app.delete("/api/todos/:id", async (req, res) => {
  const id = Number(req.params.id);

  await prisma.todo.delete({
    where: { id },
  });

  res.json({ success: true });
});

app.listen(3001, () => {
  console.log("server running on 3001");
});

| 8단계 : React에서 API 호출

이제 React에서 API를 호출합니다.

1) App.tsx

JavaScript
import { useEffect, useState } from "react";

type Todo = {
  id: number;
  title: string;
  completed: boolean;
};

function App() {
  const [todos, setTodos] = useState<Todo[]>([]);
  const [title, setTitle] = useState("");

  const fetchTodos = async () => {
    const res = await fetch("http://localhost:3001/api/todos");
    const data = await res.json();
    setTodos(data);
  };

  const createTodo = async () => {
    await fetch("http://localhost:3001/api/todos", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ title }),
    });

    setTitle("");
    fetchTodos();
  };

  useEffect(() => {
    fetchTodos();
  }, []);

  return (
    <div style={{ padding: 40 }}>
      <h1>Todo List</h1>

      <input
        value={title}
        onChange={(e) => setTitle(e.target.value)}
      />

      <button onClick={createTodo}>추가</button>

      <ul>
        {todos.map((todo) => (
          <li key={todo.id}>{todo.title}</li>
        ))}
      </ul>
    </div>
  );
}

export default App;

| 9단계 : 실행 및 테스트

1) 개발 서버 실행

: 프로젝트 루트에서 다음 명령어를 실행합니다.

code
pnpm dev

2) 접속

code
http://localhost:5173

Todo를 추가하면 다음 흐름으로 데이터가 저장됩니다.

code
React
 ↓
fetch API
 ↓
Express
 ↓
Prisma
 ↓
SQLite

그리고 API를 직접 확인할 수도 있습니다.

code
http://localhost:3001/api/todos

| 트러블슈팅

1) CORS 오류

React 개발 서버와 Express 서버는 서로 다른 origin이기 때문에 브라우저에서 API 요청이 차단될 수 있습니다.

이를 해결하기 위해 Express에 cors 미들웨어를 추가했습니다.

code
import cors from "cors";

app.use(cors());

2) Prisma Client 타입 오류

Prisma Client를 생성하지 않으면 다음 오류가 발생할 수 있습니다.

code
'@prisma/client' 모듈에 PrismaClient가 없습니다

이 경우 다음 명령어로 Prisma Client를 생성합니다.

code
npx prisma generate

| 결론

이번 글에서는 풀스택 모노레포 환경 위에서 실제 CRUD 기능을 구현하는 과정을 정리했습니다.

이를 통해 전체 스택은 다음과 같습니다.

  • React + Vite

  • Node.js + Express

  • Prisma ORM

  • SQLite

  • Turborepo

  • pnpm workspace

이 구조를 사용하면 프론트엔드와 백엔드를 하나의 프로젝트에서 함께 관리할 수 있고, 빠르게 풀스택 기능을 개발할 수 있습니다.