프로젝트 소개 및 목표
🔗 관련 에러 해결 가이드
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:password@cluster.mongodb.net/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 등 다양한 풀스택 애플리케이션으로 확장할 수 있습니다.
📚 함께 읽으면 좋은 글
REST API 서버 구축 단계별 튜토리얼 – 완성까지 한번에!
📅 2025. 10. 1.
🎯 REST API 서버 구축 단계별 튜토리얼
실시간 채팅 앱 만들기 with Socket.io – 완성까지 한번에!
📅 2025. 10. 1.
🎯 실시간 채팅 앱 만들기 with Socket.io
REST API 서버 구축 단계별 튜토리얼 – 완성까지 한번에!
📅 2025. 10. 1.
🎯 REST API 서버 구축 단계별 튜토리얼
REST API 서버 구축 단계별 튜토리얼 – 완성까지 한번에!
📅 2025. 10. 1.
🎯 REST API 서버 구축 단계별 튜토리얼
undefined 완벽 해결법 – 원인부터 예방까지
📅 2025. 9. 29.
🎯 undefined
💡 위 글들을 통해 더 깊이 있는 정보를 얻어보세요!
📢 이 글이 도움되셨나요? 공유해주세요!
여러분의 공유 한 번이 더 많은 사람들에게 도움이 됩니다 ✨
🔥 공유할 때마다 블로그 성장에 큰 힘이 됩니다! 감사합니다 🙏
💬 여러분의 소중한 의견을 들려주세요!
React + Node.js 풀스택 앱 배포하기 관련해서 궁금한 점이 더 있으시다면 언제든 물어보세요!
⭐ 모든 댓글은 24시간 내에 답변드리며, 여러분의 의견이 다른 독자들에게 큰 도움이 됩니다!
🎯 건설적인 의견과 경험 공유를 환영합니다 ✨
🔔 블로그 구독하고 최신 글을 받아보세요!
🌟 프로젝트 아이디어부터 다양한 실생활 정보까지!
매일 새로운 유용한 콘텐츠를 만나보세요 ✨
📧 RSS 구독 | 🔖 북마크 추가 | 📱 모바일 앱 알림 설정
지금 구독하고 놓치는 정보 없이 업데이트 받아보세요!