React + Node.js 풀스택 앱 배포하기 – 완성까지 한번에!

프로젝트 소개 및 목표

React + Node.js 풀스택 앱 배포하기는 현대 웹 개발의 핵심 스킬입니다. 이 가이드에서는 프론트엔드 React와 백엔드 Node.js/Express를 결합한 실전 앱을 만들고, Heroku와 Vercel 같은 클라우드 플랫폼에 배포하는 전체 과정을 다룹니다. 할 일 관리(Todo) 앱을 예시로 사용하며, 데이터베이스 연동부터 CI/CD 파이프라인 구축까지 실무에서 필요한 모든 단계를 경험할 수 있습니다. 완성된 프로젝트는 포트폴리오에 추가할 수 있는 실전 수준의 애플리케이션이 됩니다.

필요한 기술 스택

프론트엔드: React 18+, Axios (HTTP 클라이언트), React Router, Tailwind CSS

백엔드: Node.js, Express.js, MongoDB (Mongoose ORM), JWT 인증

개발 도구: Git/GitHub, npm/yarn, Postman (API 테스트), VS Code

배포 플랫폼: Vercel (프론트엔드), Render/Railway (백엔드), MongoDB Atlas (데이터베이스)

프로젝트 셋업

먼저 프로젝트 루트 디렉토리를 생성하고 클라이언트와 서버를 분리합니다:

mkdir fullstack-todo-app
cd fullstack-todo-app

# 백엔드 설정
mkdir server && cd server
npm init -y
npm install express mongoose dotenv cors jsonwebtoken bcryptjs
npm install -D nodemon

# 프론트엔드 설정
cd ..
npx create-react-app client
cd client
npm install axios react-router-dom

프로젝트 구조는 다음과 같습니다:

fullstack-todo-app/
├── server/
│   ├── models/
│   ├── routes/
│   ├── middleware/
│   └── server.js
└── client/
    ├── src/
    ├── public/
    └── package.json

단계별 구현 과정

1단계: 백엔드 API 구축 (Node.js + Express)

먼저 서버의 진입점을 만듭니다. server/server.js 파일을 생성하세요:

const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
require('dotenv').config();

const app = express();

// 미들웨어
app.use(cors());
app.use(express.json());

// MongoDB 연결
mongoose.connect(process.env.MONGODB_URI, {
  useNewUrlParser: true,
  useUnifiedTopology: true
})
.then(() => console.log('MongoDB 연결 성공'))
.catch(err => console.error('MongoDB 연결 실패:', err));

// 라우트
app.use('/api/todos', require('./routes/todos'));
app.use('/api/auth', require('./routes/auth'));

const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`서버가 포트 ${PORT}에서 실행 중`));

Todo 모델을 정의합니다. server/models/Todo.js:

const mongoose = require('mongoose');

const TodoSchema = new mongoose.Schema({
  title: { type: String, required: true },
  completed: { type: Boolean, default: false },
  userId: { type: mongoose.Schema.Types.ObjectId, ref: 'User', required: true },
  createdAt: { type: Date, default: Date.now }
});

module.exports = mongoose.model('Todo', TodoSchema);

Todo CRUD API를 만듭니다. server/routes/todos.js:

const router = require('express').Router();
const Todo = require('../models/Todo');
const auth = require('../middleware/auth');

// 모든 할 일 조회
router.get('/', auth, async (req, res) => {
  try {
    const todos = await Todo.find({ userId: req.user.id }).sort({ createdAt: -1 });
    res.json(todos);
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

// 할 일 생성
router.post('/', auth, async (req, res) => {
  const todo = new Todo({
    title: req.body.title,
    userId: req.user.id
  });
  try {
    const newTodo = await todo.save();
    res.status(201).json(newTodo);
  } catch (err) {
    res.status(400).json({ message: err.message });
  }
});

// 할 일 업데이트
router.patch('/:id', auth, async (req, res) => {
  try {
    const todo = await Todo.findOneAndUpdate(
      { _id: req.params.id, userId: req.user.id },
      { completed: req.body.completed },
      { new: true }
    );
    res.json(todo);
  } catch (err) {
    res.status(400).json({ message: err.message });
  }
});

// 할 일 삭제
router.delete('/:id', auth, async (req, res) => {
  try {
    await Todo.findOneAndDelete({ _id: req.params.id, userId: req.user.id });
    res.json({ message: '삭제되었습니다' });
  } catch (err) {
    res.status(500).json({ message: err.message });
  }
});

module.exports = router;

2단계: JWT 인증 구현

인증 미들웨어를 작성합니다. server/middleware/auth.js:

const jwt = require('jsonwebtoken');

module.exports = function(req, res, next) {
  const token = req.header('x-auth-token');
  if (!token) return res.status(401).json({ message: '인증 토큰이 없습니다' });

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    res.status(400).json({ message: '유효하지 않은 토큰입니다' });
  }
};

3단계: React 프론트엔드 개발

API 통신을 위한 유틸리티를 만듭니다. client/src/api/axios.js:

import axios from 'axios';

const instance = axios.create({
  baseURL: process.env.REACT_APP_API_URL || 'http://localhost:5000/api'
});

instance.interceptors.request.use(
  config => {
    const token = localStorage.getItem('token');
    if (token) {
      config.headers['x-auth-token'] = token;
    }
    return config;
  },
  error => Promise.reject(error)
);

export default instance;

Todo 컴포넌트를 작성합니다. client/src/components/TodoList.js:

import React, { useState, useEffect } from 'react';
import api from '../api/axios';

function TodoList() {
  const [todos, setTodos] = useState([]);
  const [newTodo, setNewTodo] = useState('');

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

  const fetchTodos = async () => {
    try {
      const response = await api.get('/todos');
      setTodos(response.data);
    } catch (err) {
      console.error(err);
    }
  };

  const addTodo = async (e) => {
    e.preventDefault();
    try {
      const response = await api.post('/todos', { title: newTodo });
      setTodos([response.data, ...todos]);
      setNewTodo('');
    } catch (err) {
      console.error(err);
    }
  };

  const toggleTodo = async (id, completed) => {
    try {
      const response = await api.patch(`/todos/${id}`, { completed: !completed });
      setTodos(todos.map(todo => todo._id === id ? response.data : todo));
    } catch (err) {
      console.error(err);
    }
  };

  const deleteTodo = async (id) => {
    try {
      await api.delete(`/todos/${id}`);
      setTodos(todos.filter(todo => todo._id !== id));
    } catch (err) {
      console.error(err);
    }
  };

  return (
    <div className="max-w-2xl mx-auto p-6">
      <h1 className="text-3xl font-bold mb-6">할 일 목록</h1>
      <form onSubmit={addTodo} className="mb-4">
        <input
          type="text"
          value={newTodo}
          onChange={(e) => setNewTodo(e.target.value)}
          className="border p-2 w-full rounded"
          placeholder="새로운 할 일 추가"
        />
      </form>
      <ul>
        {todos.map(todo => (
          <li key={todo._id} className="flex items-center gap-2 mb-2">
            <input
              type="checkbox"
              checked={todo.completed}
              onChange={() => toggleTodo(todo._id, todo.completed)}
            />
            <span className={todo.completed ? 'line-through' : ''}>{todo.title}</span>
            <button onClick={() => deleteTodo(todo._id)} className="ml-auto text-red-500">삭제</button>
          </li>
        ))}
      </ul>
    </div>
  );
}

export default TodoList;

테스트 및 배포

로컬 테스트: 먼저 MongoDB Atlas에서 무료 클러스터를 생성하고 연결 문자열을 server/.env에 추가합니다:

MONGODB_URI=mongodb+srv://username:[email protected]/todoapp
JWT_SECRET=your_secret_key_here
PORT=5000

두 개의 터미널에서 각각 실행합니다:

# 터미널 1: 백엔드
cd server
npm run dev

# 터미널 2: 프론트엔드
cd client
npm start

배포 프로세스:

1. 백엔드 배포 (Render): GitHub에 코드를 푸시한 후 Render에서 새 Web Service를 생성하고, 환경 변수를 설정합니다.

2. 프론트엔드 배포 (Vercel): Vercel CLI를 사용하거나 GitHub 연동으로 자동 배포합니다. REACT_APP_API_URL 환경 변수에 백엔드 URL을 설정합니다.

cd client
npm install -g vercel
vercel --prod

배포 후 Vercel 대시보드에서 Environment Variables에 REACT_APP_API_URL=https://your-backend.onrender.com/api를 추가합니다.

마무리 및 확장 아이디어

React + Node.js 풀스택 앱 배포하기 프로젝트를 완성했습니다! 이제 실제 사용자가 접근할 수 있는 라이브 애플리케이션을 갖게 되었습니다. 추가로 구현해볼 수 있는 기능으로는 소셜 로그인 (OAuth), 실시간 업데이트 (Socket.io), 파일 업로드 (AWS S3), 다크 모드, 드래그 앤 드롭 기능 등이 있습니다. CI/CD 파이프라인을 GitHub Actions로 구축하면 코드 푸시만으로 자동 배포가 가능합니다. 이 프로젝트를 기반으로 블로그, 전자상거래, SNS 등 다양한 풀스택 애플리케이션으로 확장할 수 있습니다.

📚 함께 읽으면 좋은 글

1

REST API 서버 구축 단계별 튜토리얼 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 1.
🎯 REST API 서버 구축 단계별 튜토리얼

2

실시간 채팅 앱 만들기 with Socket.io – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 1.
🎯 실시간 채팅 앱 만들기 with Socket.io

3

REST API 서버 구축 단계별 튜토리얼 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 1.
🎯 REST API 서버 구축 단계별 튜토리얼

4

REST API 서버 구축 단계별 튜토리얼 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 1.
🎯 REST API 서버 구축 단계별 튜토리얼

5

undefined 완벽 해결법 – 원인부터 예방까지

📂 프로젝트 아이디어
📅 2025. 9. 29.
🎯 undefined

💡 위 글들을 통해 더 깊이 있는 정보를 얻어보세요!

📢 이 글이 도움되셨나요? 공유해주세요!

여러분의 공유 한 번이 더 많은 사람들에게 도움이 됩니다 ✨

🔥 공유할 때마다 블로그 성장에 큰 힘이 됩니다! 감사합니다 🙏

💬 여러분의 소중한 의견을 들려주세요!

React + Node.js 풀스택 앱 배포하기 관련해서 궁금한 점이 더 있으시다면 언제든 물어보세요!

💡
유용한 정보 공유

궁금한 점 질문

🤝
경험담 나누기

👍
의견 표현하기

⭐ 모든 댓글은 24시간 내에 답변드리며, 여러분의 의견이 다른 독자들에게 큰 도움이 됩니다!
🎯 건설적인 의견과 경험 공유를 환영합니다 ✨

🔔 블로그 구독하고 최신 글을 받아보세요!

📚
다양한 주제
17개 카테고리

정기 업데이트
하루 3회 발행

🎯
실용적 정보
바로 적용 가능

💡
최신 트렌드
2025년 기준

🌟 프로젝트 아이디어부터 다양한 실생활 정보까지!
매일 새로운 유용한 콘텐츠를 만나보세요 ✨

📧 RSS 구독 | 🔖 북마크 추가 | 📱 모바일 앱 알림 설정
지금 구독하고 놓치는 정보 없이 업데이트 받아보세요!

답글 남기기