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

개발 에러 해결 가이드 - FixLog 노트

프로젝트 소개 및 목표

React + Node.js 풀스택 앱 배포하기는 프론트엔드부터 백엔드, 그리고 실제 서버 배포까지 전체 웹 개발 프로세스를 경험할 수 있는 실전 프로젝트입니다. 이 가이드를 통해 React로 사용자 인터페이스를 구축하고, Node.js와 Express로 RESTful API를 개발한 뒤, AWS, Heroku, Vercel 등의 클라우드 플랫폼에 실제로 배포하는 방법을 배웁니다. 단순히 로컬 환경에서만 작동하는 앱이 아닌, 전 세계 누구나 접속할 수 있는 실제 서비스를 만들어보세요. 포트폴리오에 추가할 수 있는 완성도 높은 풀스택 애플리케이션을 구축하는 것이 이 프로젝트의 최종 목표입니다.

필요한 기술 스택

이 프로젝트를 완성하기 위해 다음 기술들이 필요합니다:

  • 프론트엔드: React 18+, React Router, Axios, CSS Modules 또는 styled-components
  • 백엔드: Node.js 18+, Express.js, MongoDB 또는 PostgreSQL, JWT 인증
  • 개발 도구: Git, npm/yarn, Postman 또는 Insomnia (API 테스트용)
  • 배포 플랫폼: Vercel/Netlify (프론트엔드), Render/Railway (백엔드), MongoDB Atlas (데이터베이스)
  • 기타: Docker (선택사항), Nginx (선택사항), SSL 인증서 (Let’s Encrypt)

프로젝트 셋업

개발 환경을 구축하고 프로젝트 구조를 설정합니다.

1. 프로젝트 폴더 구조 생성

mkdir fullstack-app
cd fullstack-app
mkdir client server

2. 백엔드 초기화

cd server
npm init -y
npm install express mongoose dotenv cors bcryptjs jsonwebtoken
npm install -D nodemon

3. 프론트엔드 초기화

cd ../client
npx create-react-app .
npm install axios react-router-dom

이제 개발을 시작할 준비가 완료되었습니다. 백엔드는 server 폴더에서, 프론트엔드는 client 폴더에서 작업합니다.

단계별 구현 과정

Step 1: 백엔드 API 서버 구축

먼저 Express 서버를 설정하고 기본 라우팅을 구성합니다.

server/index.js 파일을 생성합니다:

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

dotenv.config();

const app = express();
const PORT = process.env.PORT || 5000;

// 미들웨어
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

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

// 기본 라우트
app.get('/api', (req, res) => {
  res.json({ message: 'API 서버가 정상 작동 중입니다!' });
});

// 사용자 라우트
const userRoutes = require('./routes/users');
app.use('/api/users', userRoutes);

// 게시물 라우트
const postRoutes = require('./routes/posts');
app.use('/api/posts', postRoutes);

app.listen(PORT, () => {
  console.log(`서버가 포트 ${PORT}에서 실행 중입니다.`);
});

Step 2: 데이터베이스 모델 정의

MongoDB를 사용한 User와 Post 모델을 생성합니다.

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,
    trim: 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, 10);
  next();
});

// 비밀번호 검증 메서드
userSchema.methods.comparePassword = async function(candidatePassword) {
  return await bcrypt.compare(candidatePassword, this.password);
};

module.exports = mongoose.model('User', userSchema);

server/models/Post.js:

const mongoose = require('mongoose');

const postSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true,
    trim: true
  },
  content: {
    type: String,
    required: true
  },
  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: true
  },
  tags: [String],
  likes: {
    type: Number,
    default: 0
  },
  createdAt: {
    type: Date,
    default: Date.now
  }
});

module.exports = mongoose.model('Post', postSchema);

Step 3: 인증 미들웨어 구현

server/middleware/auth.js:

const jwt = require('jsonwebtoken');

const authMiddleware = (req, res, next) => {
  try {
    const token = req.header('Authorization')?.replace('Bearer ', '');
    
    if (!token) {
      return res.status(401).json({ message: '인증 토큰이 없습니다.' });
    }

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

module.exports = authMiddleware;

Step 4: API 라우트 구현

server/routes/users.js:

const express = require('express');
const router = express.Router();
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const authMiddleware = require('../middleware/auth');

// 회원가입
router.post('/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({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: '7d' });
    
    res.status(201).json({
      message: '회원가입이 완료되었습니다.',
      token,
      user: { id: user._id, username: user.username, email: user.email }
    });
  } catch (error) {
    res.status(500).json({ message: '서버 오류가 발생했습니다.', error: error.message });
  }
});

// 로그인
router.post('/login', async (req, res) => {
  try {
    const { email, password } = req.body;
    
    const user = await User.findOne({ email });
    if (!user) {
      return res.status(401).json({ message: '이메일 또는 비밀번호가 잘못되었습니다.' });
    }

    const isPasswordValid = await user.comparePassword(password);
    if (!isPasswordValid) {
      return res.status(401).json({ message: '이메일 또는 비밀번호가 잘못되었습니다.' });
    }

    const token = jwt.sign({ userId: user._id }, process.env.JWT_SECRET, { expiresIn: '7d' });
    
    res.json({
      message: '로그인 성공',
      token,
      user: { id: user._id, username: user.username, email: user.email }
    });
  } catch (error) {
    res.status(500).json({ message: '서버 오류가 발생했습니다.', error: error.message });
  }
});

// 프로필 조회
router.get('/profile', authMiddleware, async (req, res) => {
  try {
    const user = await User.findById(req.userId).select('-password');
    res.json(user);
  } catch (error) {
    res.status(500).json({ message: '서버 오류가 발생했습니다.' });
  }
});

module.exports = router;

Step 5: React 프론트엔드 구축

이제 React + Node.js 풀스택 앱 배포하기 프로젝트의 프론트엔드를 구현합니다.

client/src/App.js:

import React from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import { AuthProvider } from './context/AuthContext';
import Navbar from './components/Navbar';
import Home from './pages/Home';
import Login from './pages/Login';
import Register from './pages/Register';
import Dashboard from './pages/Dashboard';
import PrivateRoute from './components/PrivateRoute';
import './App.css';

function App() {
  return (
    
      
        
} /> } /> } /> } /> } />
); } export default App;

client/src/context/AuthContext.js:

import React, { createContext, useState, useContext, useEffect } from 'react';
import axios from '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) {
      axios.defaults.headers.common['Authorization'] = `Bearer ${token}`;
      fetchUser();
    } else {
      setLoading(false);
    }
  }, []);

  const fetchUser = async () => {
    try {
      const response = await axios.get(`${process.env.REACT_APP_API_URL}/api/users/profile`);
      setUser(response.data);
    } catch (error) {
      console.error('사용자 정보 로드 실패:', error);
      localStorage.removeItem('token');
    } finally {
      setLoading(false);
    }
  };

  const login = async (email, password) => {
    const response = await axios.post(`${process.env.REACT_APP_API_URL}/api/users/login`, {
      email,
      password
    });
    localStorage.setItem('token', response.data.token);
    axios.defaults.headers.common['Authorization'] = `Bearer ${response.data.token}`;
    setUser(response.data.user);
    return response.data;
  };

  const register = async (username, email, password) => {
    const response = await axios.post(`${process.env.REACT_APP_API_URL}/api/users/register`, {
      username,
      email,
      password
    });
    localStorage.setItem('token', response.data.token);
    axios.defaults.headers.common['Authorization'] = `Bearer ${response.data.token}`;
    setUser(response.data.user);
    return response.data;
  };

  const logout = () => {
    localStorage.removeItem('token');
    delete axios.defaults.headers.common['Authorization'];
    setUser(null);
  };

  const value = {
    user,
    login,
    register,
    logout,
    loading
  };

  return {children};
};

Step 6: 환경 변수 설정

server/.env:

PORT=5000
MONGODB_URI=mongodb+srv://username:password@cluster.mongodb.net/fullstack-app
JWT_SECRET=your_super_secret_jwt_key_change_this_in_production

client/.env:

REACT_APP_API_URL=http://localhost:5000

테스트 및 배포

로컬 테스트

개발 환경에서 앱을 실행하여 모든 기능이 정상 작동하는지 확인합니다.

# 백엔드 실행 (server 폴더)
npm run dev

# 프론트엔드 실행 (client 폴더)
npm start

배포 프로세스

React + Node.js 풀스택 앱 배포하기의 마지막 단계입니다.

1. MongoDB Atlas 설정

  • MongoDB Atlas에서 무료 클러스터 생성
  • 네트워크 액세스 설정 (0.0.0.0/0 허용)
  • 데이터베이스 사용자 생성 및 연결 문자열 복사

2. 백엔드 배포 (Render)

  • Render.com에서 새 Web Service 생성
  • GitHub 저장소 연결
  • 환경 변수 설정 (MONGODB_URI, JWT_SECRET, PORT)
  • 빌드 명령: cd server && npm install
  • 시작 명령: node server/index.js

3. 프론트엔드 배포 (Vercel)

  • Vercel CLI 설치: npm install -g vercel
  • 클라이언트 폴더에서 vercel 명령 실행
  • 환경 변수 설정: REACT_APP_API_URL=https://your-api.render.com
  • 프로덕션 빌드 및 배포

4. CORS 설정 업데이트

// server/index.js
const corsOptions = {
  origin: ['https://your-frontend.vercel.app', 'http://localhost:3000'],
  credentials: true
};
app.use(cors(corsOptions));

마무리 및 확장 아이디어

축하합니다! React + Node.js 풀스택 앱 배포하기 프로젝트를 완성했습니다. 이제 실제 운영 환경에서 작동하는 풀스택 애플리케이션을 보유하게 되었습니다. 추가로 구현할 수 있는 기능들:

  • 파일 업로드: AWS S3 또는 Cloudinary를 이용한 이미지 업로드
  • 실시간 기능: Socket.io를 활용한 실시간 채팅 또는 알림
  • 검색 기능: Elasticsearch 또는 MongoDB의 텍스트 인덱스를 활용한 전체 텍스트 검색
  • 페이지네이션: 대용량 데이터 처리를 위한 커서 기반 또는 오프셋 기반 페이지네이션
  • CI/CD 파이프라인: GitHub Actions를 이용한 자동 테스트 및 배포
  • 모니터링: Sentry, LogRocket 등을 이용한 에러 추적 및 사용자 행동 분석

이 프로젝트는 포트폴리오에 추가하기에 완벽하며, 실제 취업 면접에서 기술 스택과 배포 경험을 어필할 수 있는 강력한 자료가 됩니다.

📚 함께 읽으면 좋은 글

1

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

📂 프로젝트 아이디어
📅 2025. 10. 16.
🎯 React + Node.js 풀스택 앱 배포하기

2

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

📂 프로젝트 아이디어
📅 2025. 10. 9.
🎯 React + Node.js 풀스택 앱 배포하기

3

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

📂 프로젝트 아이디어
📅 2025. 10. 7.
🎯 React + Node.js 풀스택 앱 배포하기

4

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

📂 프로젝트 아이디어
📅 2025. 10. 5.
🎯 React + Node.js 풀스택 앱 배포하기

5

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

📂 프로젝트 아이디어
📅 2025. 10. 3.
🎯 React + Node.js 풀스택 앱 배포하기

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

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

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


📘 페이스북


🐦 트위터


✈️ 텔레그램

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

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

React + Node.js 풀스택 앱 배포하기에 대한 여러분만의 경험이나 노하우가 있으시나요?

💡
유용한 정보 공유

궁금한 점 질문

🤝
경험담 나누기

👍
의견 표현하기

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

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

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

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

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

💡
최신 트렌드
2025년 기준

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

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

📱 전체 버전 보기