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

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

1. 프로젝트 소개 및 목표

MongoDB와 Express.js로 블로그 만들기는 풀스택 개발자를 목표로 하는 분들에게 최적의 실습 프로젝트입니다. 이 프로젝트를 통해 백엔드 API 구축부터 데이터베이스 설계, RESTful API 구현, 사용자 인증, CRUD 연산까지 실무에서 필요한 핵심 기술들을 체계적으로 학습할 수 있습니다. 완성된 블로그는 포트폴리오로 활용할 수 있으며, 회원가입, 로그인, 게시글 작성/수정/삭제, 댓글 기능 등 실제 서비스에 필요한 모든 기능을 포함합니다. Node.js 환경에서 Express.js 프레임워크와 MongoDB 데이터베이스를 활용하여 확장 가능한 웹 애플리케이션을 구축하는 방법을 배워보겠습니다.

2. 필요한 기술 스택

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

  • Node.js (v16 이상): 서버 사이드 JavaScript 런타임
  • Express.js (v4.x): 웹 애플리케이션 프레임워크
  • MongoDB (v5.x 이상): NoSQL 데이터베이스
  • Mongoose: MongoDB ODM(Object Data Modeling) 라이브러리
  • JWT: 사용자 인증을 위한 토큰 기반 인증
  • bcrypt: 비밀번호 암호화
  • EJS 또는 Pug: 템플릿 엔진 (선택사항)

3. 프로젝트 셋업

먼저 프로젝트 디렉토리를 생성하고 필요한 패키지들을 설치합니다.

# 프로젝트 디렉토리 생성
mkdir blog-project
cd blog-project

# package.json 초기화
npm init -y

# 필수 패키지 설치
npm install express mongoose dotenv bcryptjs jsonwebtoken
npm install express-validator cors helmet morgan

# 개발 의존성 패키지 설치
npm install --save-dev nodemon

프로젝트 구조를 다음과 같이 구성합니다:

blog-project/
├── config/
│   └── db.js
├── models/
│   ├── User.js
│   ├── Post.js
│   └── Comment.js
├── routes/
│   ├── auth.js
│   ├── posts.js
│   └── comments.js
├── middleware/
│   └── auth.js
├── controllers/
│   ├── authController.js
│   ├── postController.js
│   └── commentController.js
├── .env
├── server.js
└── package.json

4. 단계별 구현 과정

4.1 데이터베이스 연결 설정

먼저 MongoDB 연결을 설정합니다. .env 파일에 환경변수를 추가하세요:

MONGODB_URI=mongodb://localhost:27017/blog
JWT_SECRET=your_jwt_secret_key_here
PORT=5000

config/db.js 파일을 생성하여 데이터베이스 연결을 구현합니다:

const mongoose = require('mongoose');

const connectDB = async () => {
  try {
    await mongoose.connect(process.env.MONGODB_URI, {
      useNewUrlParser: true,
      useUnifiedTopology: true
    });
    console.log('MongoDB 연결 성공');
  } catch (error) {
    console.error('MongoDB 연결 실패:', error.message);
    process.exit(1);
  }
};

module.exports = connectDB;

4.2 데이터 모델 정의

MongoDB와 Express.js로 블로그 만들기 프로젝트에서 가장 중요한 부분은 데이터 모델 설계입니다. 사용자, 게시글, 댓글 모델을 정의합니다.

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
  },
  bio: {
    type: String,
    maxlength: 200
  },
  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);

models/Post.js:

const mongoose = require('mongoose');

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

postSchema.index({ title: 'text', content: 'text' });

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

models/Comment.js:

const mongoose = require('mongoose');

const commentSchema = new mongoose.Schema({
  content: {
    type: String,
    required: true,
    maxlength: 500
  },
  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: true
  },
  post: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'Post',
    required: true
  },
  createdAt: {
    type: Date,
    default: Date.now
  }
});

module.exports = mongoose.model('Comment', commentSchema);

4.3 인증 미들웨어 구현

JWT 기반 인증 미들웨어를 구현합니다. middleware/auth.js:

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

const auth = async (req, res, next) => {
  try {
    const token = req.header('Authorization')?.replace('Bearer ', '');
    
    if (!token) {
      return res.status(401).json({ error: '인증 토큰이 필요합니다' });
    }

    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    const user = await User.findById(decoded.userId).select('-password');
    
    if (!user) {
      return res.status(401).json({ error: '사용자를 찾을 수 없습니다' });
    }

    req.user = user;
    req.userId = user._id;
    next();
  } catch (error) {
    res.status(401).json({ error: '유효하지 않은 토큰입니다' });
  }
};

module.exports = auth;

4.4 컨트롤러 구현

controllers/authController.js에서 회원가입과 로그인 로직을 구현합니다:

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

// 회원가입
exports.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({ error: '이미 존재하는 사용자입니다' });
    }

    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({ 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({ error: '이메일 또는 비밀번호가 올바르지 않습니다' });
    }

    const isMatch = await user.comparePassword(password);
    if (!isMatch) {
      return res.status(401).json({ error: '이메일 또는 비밀번호가 올바르지 않습니다' });
    }

    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({ error: error.message });
  }
};

controllers/postController.js에서 게시글 CRUD를 구현합니다:

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

// 게시글 생성
exports.createPost = async (req, res) => {
  try {
    const { title, content, tags } = req.body;
    const post = new Post({
      title,
      content,
      tags,
      author: req.userId
    });
    await post.save();
    await post.populate('author', 'username email');
    res.status(201).json({ message: '게시글 생성 성공', post });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
};

// 게시글 목록 조회
exports.getPosts = async (req, res) => {
  try {
    const { page = 1, limit = 10, tag } = req.query;
    const query = tag ? { tags: tag } : {};
    
    const posts = await Post.find(query)
      .populate('author', 'username')
      .sort({ createdAt: -1 })
      .limit(limit * 1)
      .skip((page - 1) * limit)
      .exec();

    const count = await Post.countDocuments(query);

    res.json({
      posts,
      totalPages: Math.ceil(count / limit),
      currentPage: page
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
};

// 게시글 상세 조회
exports.getPost = async (req, res) => {
  try {
    const post = await Post.findByIdAndUpdate(
      req.params.id,
      { $inc: { viewCount: 1 } },
      { new: true }
    ).populate('author', 'username email bio');

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

    res.json(post);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
};

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

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

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

    post.title = title || post.title;
    post.content = content || post.content;
    post.tags = tags || post.tags;
    post.updatedAt = Date.now();

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

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

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

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

    await post.deleteOne();
    res.json({ message: '게시글 삭제 성공' });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
};

4.5 라우트 설정

MongoDB와 Express.js로 블로그 만들기의 핵심인 API 라우트를 설정합니다. routes/auth.js:

const express = require('express');
const router = express.Router();
const { register, login } = require('../controllers/authController');

router.post('/register', register);
router.post('/login', login);

module.exports = router;

routes/posts.js:

const express = require('express');
const router = express.Router();
const auth = require('../middleware/auth');
const {
  createPost,
  getPosts,
  getPost,
  updatePost,
  deletePost
} = require('../controllers/postController');

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

module.exports = router;

4.6 서버 설정

마지막으로 server.js에서 모든 것을 통합합니다:

require('dotenv').config();
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const connectDB = require('./config/db');

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

const app = express();

// 데이터베이스 연결
connectDB();

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

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

// 에러 핸들링
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).json({ error: '서버 오류가 발생했습니다' });
});

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

5. 테스트 및 배포

5.1 API 테스트

Postman이나 Thunder Client를 사용하여 API를 테스트합니다:

# 서버 실행
npm run dev

# 회원가입 테스트
POST http://localhost:5000/api/auth/register
Content-Type: application/json

{
  "username": "testuser",
  "email": "[email protected]",
  "password": "password123"
}

# 게시글 생성 테스트 (토큰 필요)
POST http://localhost:5000/api/posts
Authorization: Bearer YOUR_JWT_TOKEN
Content-Type: application/json

{
  "title": "첫 번째 게시글",
  "content": "안녕하세요, 이것은 테스트 게시글입니다.",
  "tags": ["테스트", "블로그"]
}

5.2 배포 준비

Heroku, AWS, 또는 DigitalOcean에 배포할 수 있습니다. MongoDB Atlas를 사용하면 클라우드 데이터베이스를 쉽게 설정할 수 있습니다:

# MongoDB Atlas 연결 문자열 예시
MONGODB_URI=mongodb+srv://username:[email protected]/blog?retryWrites=true&w=majority

package.json에 시작 스크립트를 추가합니다:

"scripts": {
  "start": "node server.js",
  "dev": "nodemon server.js"
}

6. 마무리 및 확장 아이디어

축하합니다! MongoDB와 Express.js로 블로그 만들기 프로젝트를 완성했습니다. 이제 기본적인 블로그 시스템이 구축되었으며, 다음과 같은 기능들을 추가하여 확장할 수 있습니다:

  • 댓글 시스템: 게시글에 댓글 작성 기능 추가
  • 이미지 업로드: Multer와 Cloudinary를 사용한 이미지 업로드
  • 검색 기능: MongoDB 텍스트 인덱스를 활용한 전체 검색
  • 좋아요 기능: 게시글 좋아요/북마크 기능
  • 소셜 로그인: Google, GitHub OAuth 인증
  • 이메일 인증: Nodemailer를 사용한 회원가입 이메일 인증
  • 관리자 페이지: 사용자 및 게시글 관리 대시보드
  • 프론트엔드 연동: React, Vue, Next.js와 연동

이 프로젝트는 포트폴리오로 활용하기에 완벽하며, 실무에서 사용되는 기술 스택과 아키텍처 패턴을 익힐 수 있는 훌륭한 학습 자료입니다. 계속해서 기능을 추가하고 개선하면서 자신만의 독특한 블로그 플랫폼을 만들어보세요!

📚 함께 읽으면 좋은 글

1

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

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

2

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

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

3

JWT 인증 시스템 구현하기 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 9.
🎯 JWT 인증 시스템 구현하기

4

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

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

5

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

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

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

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

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

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

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

MongoDB와 Express.js로 블로그 만들기 관련해서 궁금한 점이 더 있으시다면 언제든 물어보세요!

💡
유용한 정보 공유

궁금한 점 질문

🤝
경험담 나누기

👍
의견 표현하기

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

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

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

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

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

💡
최신 트렌드
2025년 기준

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

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

답글 남기기