프로젝트 소개 및 목표
🔗 관련 에러 해결 가이드
React + Node.js 풀스택 앱 배포하기는 프론트엔드부터 백엔드, 그리고 실제 배포까지 전 과정을 경험할 수 있는 실전 프로젝트입니다. 이 가이드를 통해 React로 사용자 인터페이스를 구축하고, Node.js와 Express로 RESTful API 서버를 만들며, MongoDB로 데이터를 관리하는 완전한 웹 애플리케이션을 개발합니다. 최종적으로 Vercel과 Render를 활용해 무료로 배포하는 방법까지 배우게 됩니다. 이 프로젝트는 포트폴리오에 추가할 수 있는 실용적인 풀스택 애플리케이션을 만들고, 실무에서 필요한 배포 경험을 쌓을 수 있는 최적의 학습 과정입니다.
필요한 기술 스택
이 프로젝트를 구현하기 위해 다음 기술들이 필요합니다:
- 프론트엔드: React 18+, React Router, Axios, Tailwind CSS
- 백엔드: Node.js 18+, Express.js, MongoDB, Mongoose
- 인증: JWT (JSON Web Token), bcrypt
- 배포: Vercel (프론트엔드), Render (백엔드), MongoDB Atlas (데이터베이스)
- 개발 도구: Git, npm/yarn, Postman, VS Code
Node.js와 React에 대한 기본적인 이해가 있다면 충분히 따라올 수 있습니다.
프로젝트 셋업
먼저 프로젝트 구조를 설정하겠습니다. 하나의 저장소에 클라이언트와 서버를 분리하는 모노레포 구조를 사용합니다.
# 프로젝트 루트 디렉토리 생성
mkdir fullstack-deploy-app
cd fullstack-deploy-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 react-router-dom axios
npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p
프로젝트 루트에 .gitignore 파일을 생성하여 node_modules/, .env 파일을 제외합니다. 이제 기본 구조가 완성되었으니 본격적인 구현을 시작하겠습니다.
단계별 구현 과정
1단계: MongoDB Atlas 설정 및 백엔드 모델 생성
먼저 MongoDB Atlas에서 무료 클러스터를 생성하고 연결 문자열을 받습니다. server/.env 파일을 생성합니다:
MONGODB_URI=mongodb+srv://username:password@cluster.mongodb.net/fullstack-app
JWT_SECRET=your_super_secret_key_here
PORT=5000
사용자 모델을 정의합니다 (server/models/User.js):
const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');
const userSchema = new mongoose.Schema({
username: {
type: String,
required: true,
unique: true,
trim: true,
minlength: 3
},
email: {
type: String,
required: true,
unique: true,
lowercase: true
},
password: {
type: String,
required: true,
minlength: 6
},
createdAt: {
type: Date,
default: Date.now
}
});
// 비밀번호 해싱 미들웨어
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
this.password = await bcrypt.hash(this.password, 12);
next();
});
// 비밀번호 검증 메서드
userSchema.methods.comparePassword = async function(candidatePassword) {
return await bcrypt.compare(candidatePassword, this.password);
};
module.exports = mongoose.model('User', userSchema);
2단계: Express 서버 및 인증 API 구현
server/index.js 파일을 생성하여 Express 서버를 설정합니다:
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const jwt = require('jsonwebtoken');
require('dotenv').config();
const User = require('./models/User');
const app = express();
// 미들웨어
app.use(cors());
app.use(express.json());
// MongoDB 연결
mongoose.connect(process.env.MONGODB_URI)
.then(() => console.log('MongoDB 연결 성공'))
.catch(err => console.error('MongoDB 연결 실패:', err));
// JWT 인증 미들웨어
const authenticateToken = (req, res, next) => {
const token = req.headers['authorization']?.split(' ')[1];
if (!token) return res.status(401).json({ message: '인증 토큰이 필요합니다' });
jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
if (err) return res.status(403).json({ message: '유효하지 않은 토큰입니다' });
req.user = user;
next();
});
};
// 회원가입 API
app.post('/api/auth/register', async (req, res) => {
try {
const { username, email, password } = req.body;
const existingUser = await User.findOne({ $or: [{ email }, { username }] });
if (existingUser) {
return res.status(400).json({ message: '이미 존재하는 사용자입니다' });
}
const user = new User({ username, email, password });
await user.save();
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '7d' });
res.status(201).json({ token, user: { id: user._id, username, email } });
} catch (error) {
res.status(500).json({ message: '회원가입 실패', error: error.message });
}
});
// 로그인 API
app.post('/api/auth/login', async (req, res) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user || !(await user.comparePassword(password))) {
return res.status(401).json({ message: '이메일 또는 비밀번호가 잘못되었습니다' });
}
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, { expiresIn: '7d' });
res.json({ token, user: { id: user._id, username: user.username, email } });
} catch (error) {
res.status(500).json({ message: '로그인 실패', error: error.message });
}
});
// 보호된 라우트 예제
app.get('/api/user/profile', authenticateToken, async (req, res) => {
try {
const user = await User.findById(req.user.id).select('-password');
res.json(user);
} catch (error) {
res.status(500).json({ message: '사용자 정보 조회 실패' });
}
});
const PORT = process.env.PORT || 5000;
app.listen(PORT, () => console.log(`서버가 포트 ${PORT}에서 실행 중입니다`));
server/package.json에 스크립트를 추가합니다:
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js"
}
3단계: React 프론트엔드 구현
먼저 Tailwind CSS를 설정합니다 (client/tailwind.config.js):
module.exports = {
content: ["./src/**/*.{js,jsx,ts,tsx}"],
theme: {
extend: {},
},
plugins: [],
}
client/src/index.css에 Tailwind 지시문을 추가합니다:
@tailwind base;
@tailwind components;
@tailwind utilities;
API 통신을 위한 axios 인스턴스를 생성합니다 (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.Authorization = `Bearer ${token}`;
}
return config;
});
export default instance;
인증 컨텍스트를 생성합니다 (client/src/context/AuthContext.js):
import React, { createContext, useState, useContext, useEffect } from 'react';
import axios from '../api/axios';
const AuthContext = createContext();
export const useAuth = () => useContext(AuthContext);
export const AuthProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const token = localStorage.getItem('token');
if (token) {
loadUser();
} else {
setLoading(false);
}
}, []);
const loadUser = async () => {
try {
const response = await axios.get('/user/profile');
setUser(response.data);
} catch (error) {
localStorage.removeItem('token');
} finally {
setLoading(false);
}
};
const login = async (email, password) => {
const response = await axios.post('/auth/login', { email, password });
localStorage.setItem('token', response.data.token);
setUser(response.data.user);
return response.data;
};
const register = async (username, email, password) => {
const response = await axios.post('/auth/register', { username, email, password });
localStorage.setItem('token', response.data.token);
setUser(response.data.user);
return response.data;
};
const logout = () => {
localStorage.removeItem('token');
setUser(null);
};
return (
{children}
);
};
로그인 컴포넌트를 구현합니다 (client/src/pages/Login.js):
import React, { useState } from 'react';
import { useNavigate, Link } from 'react-router-dom';
import { useAuth } from '../context/AuthContext';
const Login = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [error, setError] = useState('');
const { login } = useAuth();
const navigate = useNavigate();
const handleSubmit = async (e) => {
e.preventDefault();
setError('');
try {
await login(email, password);
navigate('/dashboard');
} catch (err) {
setError(err.response?.data?.message || '로그인에 실패했습니다');
}
};
return (
로그인
{error && {error}}
계정이 없으신가요? 회원가입
);
};
export default Login;
4단계: 라우팅 및 메인 앱 구성
client/src/App.js를 구성합니다:
import React from 'react';
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { AuthProvider, useAuth } from './context/AuthContext';
import Login from './pages/Login';
import Register from './pages/Register';
import Dashboard from './pages/Dashboard';
const PrivateRoute = ({ children }) => {
const { user, loading } = useAuth();
if (loading) return 로딩 중...;
return user ? children : ;
};
function App() {
return (
} />
} />
} />
} />
);
}
export default App;
테스트 및 배포
로컬 테스트
먼저 로컬 환경에서 애플리케이션을 테스트합니다:
# 터미널 1: 백엔드 실행
cd server
npm run dev
# 터미널 2: 프론트엔드 실행
cd client
npm start
브라우저에서 http://localhost:3000으로 접속하여 회원가입, 로그인, 대시보드 접근을 테스트합니다.
백엔드 배포 (Render)
React + Node.js 풀스택 앱 배포하기의 핵심 단계입니다. Render.com에 접속하여 다음 단계를 진행합니다:
- New > Web Service 선택
- GitHub 저장소 연결 (server 폴더)
- 환경 설정:
- Build Command:
npm install - Start Command:
npm start - Environment Variables에
MONGODB_URI,JWT_SECRET추가
- Build Command:
- Create Web Service 클릭
배포된 URL을 복사합니다 (예: https://your-app.onrender.com).
프론트엔드 배포 (Vercel)
client/.env.production 파일을 생성합니다:
REACT_APP_API_URL=https://your-app.onrender.com/api
Vercel CLI로 배포합니다:
npm install -g vercel
cd client
vercel --prod
또는 Vercel 웹사이트에서 GitHub 저장소를 연결하여 자동 배포를 설정할 수 있습니다. 환경 변수 REACT_APP_API_URL을 Vercel 대시보드에서 설정해야 합니다.
마무리 및 확장 아이디어
축하합니다! React + Node.js 풀스택 앱 배포하기 프로젝트를 완성했습니다. 이제 실제 운영 환경에서 동작하는 풀스택 애플리케이션을 가지게 되었습니다. 이 기본 구조를 바탕으로 다음과 같은 기능을 추가할 수 있습니다:
- 소셜 로그인: Google, GitHub OAuth 통합
- 파일 업로드: Cloudinary 또는 AWS S3 활용
- 실시간 기능: Socket.io로 채팅 또는 알림 구현
- 데이터 관리: CRUD 기능이 있는 게시판 또는 할 일 목록 추가
- 페이지네이션 및 검색: 데이터 조회 기능 향상
- CI/CD: GitHub Actions로 자동 배포 파이프라인 구축
이 프로젝트는 포트폴리오의 핵심 항목이 될 수 있으며, 실무에서 요구하는 배포 경험을 제공합니다. 계속해서 기능을 추가하고 개선하면서 실력을 키워나가세요!
📚 함께 읽으면 좋은 글
React + Node.js 풀스택 앱 배포하기 – 완성까지 한번에!
📅 2025. 10. 7.
🎯 React + Node.js 풀스택 앱 배포하기
React + Node.js 풀스택 앱 배포하기 – 완성까지 한번에!
📅 2025. 10. 5.
🎯 React + Node.js 풀스택 앱 배포하기
React + Node.js 풀스택 앱 배포하기 – 완성까지 한번에!
📅 2025. 10. 3.
🎯 React + Node.js 풀스택 앱 배포하기
React + Node.js 풀스택 앱 배포하기 – 완성까지 한번에!
📅 2025. 10. 2.
🎯 React + Node.js 풀스택 앱 배포하기
30분만에 만드는 Todo App 완성 가이드 – 완성까지 한번에!
📅 2025. 10. 5.
🎯 30분만에 만드는 Todo App 완성 가이드
💡 위 글들을 통해 더 깊이 있는 정보를 얻어보세요!
📢 이 글이 도움되셨나요? 공유해주세요!
여러분의 공유 한 번이 더 많은 사람들에게 도움이 됩니다 ✨
🔥 공유할 때마다 블로그 성장에 큰 힘이 됩니다! 감사합니다 🙏
💬 여러분의 소중한 의견을 들려주세요!
이 글을 읽고 새롭게 알게 된 정보가 있다면 공유해주세요!
⭐ 모든 댓글은 24시간 내에 답변드리며, 여러분의 의견이 다른 독자들에게 큰 도움이 됩니다!
🎯 건설적인 의견과 경험 공유를 환영합니다 ✨
🔔 블로그 구독하고 최신 글을 받아보세요!
🌟 프로젝트 아이디어부터 다양한 실생활 정보까지!
매일 새로운 유용한 콘텐츠를 만나보세요 ✨
📧 RSS 구독 | 🔖 북마크 추가 | 📱 모바일 앱 알림 설정
지금 구독하고 놓치는 정보 없이 업데이트 받아보세요!