MongoDB와 Express.js로 블로그 만들기 – 완성까지 한번에!

프로젝트 소개 및 목표

MongoDB와 Express.js로 블로그 만들기는 풀스택 개발의 핵심 기술을 익힐 수 있는 실전 프로젝트입니다. 이 가이드에서는 Node.js 백엔드 프레임워크인 Express.js와 NoSQL 데이터베이스 MongoDB를 활용하여 완전한 기능을 갖춘 블로그 시스템을 구축합니다. 사용자 인증, 게시글 CRUD, 댓글 기능, 이미지 업로드 등 실제 서비스에 필요한 모든 요소를 단계별로 구현하며, 완성된 프로젝트는 포트폴리오로 활용할 수 있습니다. RESTful API 설계 원칙을 배우고 MongoDB의 스키마 설계 방법을 익히면서 백엔드 개발 역량을 크게 향상시킬 수 있습니다.

필요한 기술 스택

이 프로젝트를 시작하기 위해서는 다음과 같은 기술 스택이 필요합니다. Node.js (v14 이상)와 npm 패키지 매니저를 설치해야 하며, MongoDB는 로컬 설치 또는 MongoDB Atlas 클라우드 서비스를 사용할 수 있습니다. Express.js는 웹 서버와 라우팅을 담당하고, Mongoose는 MongoDB와의 연결 및 스키마 정의를 위해 사용됩니다. 추가로 사용자 인증을 위한 JWT(jsonwebtoken), 비밀번호 암호화를 위한 bcrypt, 환경 변수 관리를 위한 dotenv, 파일 업로드를 위한 multer 등의 npm 패키지가 필요합니다.

프로젝트 셋업

먼저 프로젝트 디렉토리를 생성하고 npm을 초기화합니다. 터미널에서 원하는 위치에 폴더를 만들고 npm init 명령어를 실행하여 package.json 파일을 생성합니다. 이후 필요한 모든 의존성 패키지를 설치합니다. 프로젝트 구조는 MVC 패턴을 따라 models, controllers, routes, middleware 폴더로 분리하여 관리합니다. 환경 변수를 관리하기 위해 .env 파일을 생성하고 MongoDB 연결 문자열, JWT 시크릿 키, 포트 번호 등을 설정합니다. Git 저장소를 초기화하고 .gitignore 파일에 node_modules와 .env를 추가하여 민감한 정보가 업로드되지 않도록 합니다. MongoDB Atlas를 사용하는 경우 클러스터를 생성하고 연결 문자열을 복사해둡니다.

단계별 구현 과정

1단계: Express 서버 및 MongoDB 연결 설정

프로젝트의 진입점인 server.js 파일을 생성하고 Express 애플리케이션을 초기화합니다. Mongoose를 사용하여 MongoDB에 연결하는 코드를 작성합니다.

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

dotenv.config();

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

// 미들웨어 설정
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.listen(PORT, () => {
  console.log(`서버가 포트 ${PORT}에서 실행중입니다`);
});

2단계: 데이터 모델 정의

Mongoose 스키마를 사용하여 User, Post, Comment 모델을 정의합니다. 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
  },
  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, 10);
  next();
});

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

다음으로 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],
  thumbnail: String,
  likes: [{
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User'
  }],
  createdAt: {
    type: Date,
    default: Date.now
  },
  updatedAt: {
    type: Date,
    default: Date.now
  }
});

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

3단계: 사용자 인증 시스템 구현

JWT를 사용한 회원가입과 로그인 기능을 구현합니다. controllers/authController.js 파일을 생성합니다.

const User = require('../models/User');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcryptjs');

// 회원가입
exports.register = async (req, res) => {
  try {
    const { username, email, password } = req.body;
    
    const existingUser = await User.findOne({ email });
    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({ token, userId: user._id, username: user.username });
  } catch (error) {
    res.status(500).json({ message: '서버 오류', error: error.message });
  }
};

// 로그인
exports.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 isMatch = await bcrypt.compare(password, user.password);
    if (!isMatch) {
      return res.status(401).json({ message: '이메일 또는 비밀번호가 잘못되었습니다' });
    }

    const token = jwt.sign(
      { userId: user._id },
      process.env.JWT_SECRET,
      { expiresIn: '7d' }
    );

    res.json({ token, userId: user._id, username: user.username });
  } catch (error) {
    res.status(500).json({ message: '서버 오류', error: error.message });
  }
};

인증 미들웨어를 작성하여 보호된 라우트에서 사용합니다. middleware/auth.js 파일을 생성합니다.

const jwt = require('jsonwebtoken');

module.exports = (req, res, next) => {
  try {
    const token = req.headers.authorization?.split(' ')[1];
    
    if (!token) {
      return res.status(401).json({ message: '인증 토큰이 없습니다' });
    }

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

4단계: 게시글 CRUD API 구현

MongoDB와 Express.js로 블로그 만들기의 핵심인 게시글 관리 기능을 구현합니다. controllers/postController.js 파일을 생성합니다.

const Post = require('../models/Post');

// 게시글 생성
exports.createPost = async (req, res) => {
  try {
    const { title, content, tags } = req.body;
    const post = new Post({
      title,
      content,
      tags: tags ? tags.split(',').map(tag => tag.trim()) : [],
      author: req.userData.userId
    });

    await post.save();
    res.status(201).json(post);
  } catch (error) {
    res.status(500).json({ message: '게시글 생성 실패', error: error.message });
  }
};

// 전체 게시글 조회 (페이지네이션)
exports.getAllPosts = async (req, res) => {
  try {
    const page = parseInt(req.query.page) || 1;
    const limit = parseInt(req.query.limit) || 10;
    const skip = (page - 1) * limit;

    const posts = await Post.find()
      .populate('author', 'username')
      .sort({ createdAt: -1 })
      .skip(skip)
      .limit(limit);

    const total = await Post.countDocuments();

    res.json({
      posts,
      currentPage: page,
      totalPages: Math.ceil(total / limit),
      totalPosts: total
    });
  } catch (error) {
    res.status(500).json({ message: '게시글 조회 실패', error: error.message });
  }
};

// 게시글 수정
exports.updatePost = async (req, res) => {
  try {
    const { id } = req.params;
    const { title, content, tags } = req.body;

    const post = await Post.findById(id);
    if (!post) {
      return res.status(404).json({ message: '게시글을 찾을 수 없습니다' });
    }

    if (post.author.toString() !== req.userData.userId) {
      return res.status(403).json({ message: '권한이 없습니다' });
    }

    post.title = title || post.title;
    post.content = content || post.content;
    post.tags = tags ? tags.split(',').map(tag => tag.trim()) : post.tags;
    post.updatedAt = Date.now();

    await post.save();
    res.json(post);
  } catch (error) {
    res.status(500).json({ message: '게시글 수정 실패', error: error.message });
  }
};

// 게시글 삭제
exports.deletePost = async (req, res) => {
  try {
    const { id } = req.params;
    const post = await Post.findById(id);

    if (!post) {
      return res.status(404).json({ message: '게시글을 찾을 수 없습니다' });
    }

    if (post.author.toString() !== req.userData.userId) {
      return res.status(403).json({ message: '권한이 없습니다' });
    }

    await post.deleteOne();
    res.json({ message: '게시글이 삭제되었습니다' });
  } catch (error) {
    res.status(500).json({ message: '게시글 삭제 실패', error: error.message });
  }
};

5단계: 라우트 설정

routes/auth.js와 routes/posts.js 파일을 생성하여 API 엔드포인트를 정의합니다.

// routes/posts.js
const express = require('express');
const router = express.Router();
const postController = require('../controllers/postController');
const auth = require('../middleware/auth');

router.post('/', auth, postController.createPost);
router.get('/', postController.getAllPosts);
router.put('/:id', auth, postController.updatePost);
router.delete('/:id', auth, postController.deletePost);

module.exports = router;

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

const authRoutes = require('./routes/auth');
const postRoutes = require('./routes/posts');

app.use('/api/auth', authRoutes);
app.use('/api/posts', postRoutes);

테스트 및 배포

API 테스트는 Postman 또는 Thunder Client를 사용하여 진행합니다. 먼저 /api/auth/register 엔드포인트로 회원가입을 테스트하고, /api/auth/login으로 로그인하여 JWT 토큰을 받습니다. 받은 토큰을 Authorization 헤더에 Bearer 토큰으로 추가하여 게시글 생성, 수정, 삭제 기능을 테스트합니다. 배포는 Heroku, Railway, 또는 AWS EC2를 사용할 수 있습니다. 환경 변수를 배포 플랫폼에 설정하고, MongoDB Atlas의 네트워크 접근 설정에서 배포 서버의 IP를 허용합니다. package.json에 start 스크립트를 추가하고 Node.js 버전을 명시합니다. CORS 설정을 추가하여 프론트엔드에서 API를 호출할 수 있도록 합니다.

마무리 및 확장 아이디어

MongoDB와 Express.js로 블로그 만들기 프로젝트를 완성했습니다. 기본 기능을 확장하여 댓글 시스템, 좋아요 기능, 검색 및 필터링, 이미지 업로드, 마크다운 에디터 통합 등을 추가할 수 있습니다. React나 Vue.js로 프론트엔드를 구축하면 완전한 풀스택 블로그 애플리케이션이 됩니다. Redis를 추가하여 캐싱을 구현하거나, Elasticsearch로 고급 검색 기능을 추가하는 것도 좋은 학습 경험이 됩니다. 이 프로젝트는 포트폴리오에 추가하기 좋은 실전 예제이며, RESTful API 설계와 NoSQL 데이터베이스 활용 경험을 증명할 수 있습니다.

📚 함께 읽으면 좋은 글

1

MongoDB와 Express.js로 블로그 만들기 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 22.
🎯 MongoDB와 Express.js로 블로그 만들기

2

MongoDB와 Express.js로 블로그 만들기 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 14.
🎯 MongoDB와 Express.js로 블로그 만들기

3

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

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

4

30분만에 만드는 Todo App 완성 가이드 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 28.
🎯 30분만에 만드는 Todo App 완성 가이드

5

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

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

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

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

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

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

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

여러분은 MongoDB와 Express.js로 블로그 만들기에 대해 어떻게 생각하시나요?

💡
유용한 정보 공유

궁금한 점 질문

🤝
경험담 나누기

👍
의견 표현하기

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

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

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

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

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

💡
최신 트렌드
2025년 기준

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

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

답글 남기기