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

프로젝트 소개 및 목표

React + Node.js 풀스택 앱 배포하기는 현대 웹 개발의 핵심 기술을 한 번에 경험할 수 있는 실전 프로젝트입니다. 이 가이드를 통해 프론트엔드부터 백엔드, 데이터베이스 연동, 그리고 실제 서버 배포까지 전체 개발 프로세스를 완성하게 됩니다. React로 동적인 사용자 인터페이스를 구축하고, Node.js와 Express로 RESTful API 서버를 만들며, MongoDB를 활용한 데이터 관리, 마지막으로 클라우드 플랫폼에 배포하여 실제 운영 환경을 경험합니다. 포트폴리오로 활용할 수 있는 완성도 높은 풀스택 애플리케이션을 만들어보세요.

필요한 기술 스택

이 프로젝트를 진행하기 위해서는 다음 기술들이 필요합니다:

  • 프론트엔드: React 18+, Axios, React Router, Styled-components
  • 백엔드: Node.js 18+, Express.js, Mongoose, dotenv
  • 데이터베이스: MongoDB Atlas (클라우드 DB)
  • 배포 플랫폼: Vercel (프론트엔드), Render 또는 Railway (백엔드)
  • 개발 도구: Git, VS Code, Postman (API 테스트)

Node.js와 npm이 설치되어 있어야 하며, 기본적인 JavaScript, React, Express 지식이 있으면 더욱 수월하게 진행할 수 있습니다.

프로젝트 셋업

먼저 프로젝트 디렉토리를 생성하고 초기 설정을 진행합니다. 터미널에서 다음 명령어를 실행하세요:

# 프로젝트 폴더 생성
mkdir fullstack-app
cd fullstack-app

# 백엔드 초기화
mkdir backend
cd backend
npm init -y
npm install express mongoose dotenv cors nodemon

# 프론트엔드 생성
cd ..
npx create-react-app frontend
cd frontend
npm install axios react-router-dom styled-components

package.json의 scripts 부분을 수정하여 개발 서버를 편리하게 실행할 수 있도록 설정합니다. 백엔드의 경우 nodemon을 사용하여 자동 재시작 기능을 활성화합니다.

단계별 구현 과정

1단계: MongoDB 설정 및 연결

MongoDB Atlas에서 무료 클러스터를 생성합니다. atlas.mongodb.com에 접속하여 계정을 만들고 새 클러스터를 생성한 후 연결 문자열을 복사합니다. backend 폴더에 .env 파일을 생성하고 다음과 같이 설정합니다:

MONGODB_URI=mongodb+srv://username:[email protected]/myapp
PORT=5000
NODE_ENV=development

backend/server.js 파일을 생성하고 Express 서버와 MongoDB 연결을 설정합니다:

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)
  .then(() => console.log('MongoDB 연결 성공'))
  .catch(err => console.error('MongoDB 연결 실패:', err));

// 기본 라우트
app.get('/', (req, res) => {
  res.json({ message: '풀스택 앱 API 서버' });
});

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

2단계: 백엔드 API 구축

간단한 할일 관리 앱을 만들어봅니다. backend/models/Todo.js 파일을 생성하여 데이터 모델을 정의합니다:

const mongoose = require('mongoose');

const todoSchema = new mongoose.Schema({
  title: {
    type: String,
    required: true,
    trim: true
  },
  description: {
    type: String,
    trim: true
  },
  completed: {
    type: Boolean,
    default: false
  },
  createdAt: {
    type: Date,
    default: Date.now
  }
});

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

backend/routes/todos.js 파일을 생성하여 CRUD API를 구현합니다:

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

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

// 할일 생성
router.post('/', async (req, res) => {
  const todo = new Todo({
    title: req.body.title,
    description: req.body.description
  });

  try {
    const newTodo = await todo.save();
    res.status(201).json(newTodo);
  } catch (error) {
    res.status(400).json({ message: error.message });
  }
});

// 할일 수정
router.patch('/:id', async (req, res) => {
  try {
    const todo = await Todo.findById(req.params.id);
    if (!todo) return res.status(404).json({ message: '할일을 찾을 수 없습니다' });

    if (req.body.title) todo.title = req.body.title;
    if (req.body.description) todo.description = req.body.description;
    if (req.body.completed !== undefined) todo.completed = req.body.completed;

    const updatedTodo = await todo.save();
    res.json(updatedTodo);
  } catch (error) {
    res.status(400).json({ message: error.message });
  }
});

// 할일 삭제
router.delete('/:id', async (req, res) => {
  try {
    const todo = await Todo.findByIdAndDelete(req.params.id);
    if (!todo) return res.status(404).json({ message: '할일을 찾을 수 없습니다' });
    res.json({ message: '할일이 삭제되었습니다' });
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
});

module.exports = router;

server.js에 라우트를 연결합니다:

const todoRoutes = require('./routes/todos');
app.use('/api/todos', todoRoutes);

3단계: React 프론트엔드 구축

frontend/src/services/api.js 파일을 생성하여 API 통신 함수를 작성합니다:

import axios from 'axios';

const API_URL = process.env.REACT_APP_API_URL || 'http://localhost:5000/api';

const api = axios.create({
  baseURL: API_URL,
  headers: {
    'Content-Type': 'application/json'
  }
});

export const todoAPI = {
  getAll: () => api.get('/todos'),
  create: (data) => api.post('/todos', data),
  update: (id, data) => api.patch(`/todos/${id}`, data),
  delete: (id) => api.delete(`/todos/${id}`)
};

export default api;

frontend/src/components/TodoList.js 파일을 생성하여 할일 목록 컴포넌트를 만듭니다:

import React, { useState, useEffect } from 'react';
import styled from 'styled-components';
import { todoAPI } from '../services/api';

const TodoList = () => {
  const [todos, setTodos] = useState([]);
  const [title, setTitle] = useState('');
  const [description, setDescription] = useState('');
  const [loading, setLoading] = useState(false);

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

  const fetchTodos = async () => {
    try {
      setLoading(true);
      const response = await todoAPI.getAll();
      setTodos(response.data);
    } catch (error) {
      console.error('할일 불러오기 실패:', error);
    } finally {
      setLoading(false);
    }
  };

  const handleSubmit = async (e) => {
    e.preventDefault();
    if (!title.trim()) return;

    try {
      await todoAPI.create({ title, description });
      setTitle('');
      setDescription('');
      fetchTodos();
    } catch (error) {
      console.error('할일 생성 실패:', error);
    }
  };

  const toggleComplete = async (id, completed) => {
    try {
      await todoAPI.update(id, { completed: !completed });
      fetchTodos();
    } catch (error) {
      console.error('할일 수정 실패:', error);
    }
  };

  const deleteTodo = async (id) => {
    try {
      await todoAPI.delete(id);
      fetchTodos();
    } catch (error) {
      console.error('할일 삭제 실패:', error);
    }
  };

  return (
    
      

할일 관리 앱

setTitle(e.target.value)} />