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

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

프로젝트 소개 및 목표

MongoDB와 Express.js로 블로그 만들기는 백엔드 개발의 핵심 기술을 실전에서 배울 수 있는 최고의 프로젝트입니다. 이 가이드를 통해 RESTful API 설계, NoSQL 데이터베이스 연동, 사용자 인증, CRUD 작업 등 실무에서 필요한 모든 기술을 습득할 수 있습니다. 완성된 블로그 시스템은 포트폴리오로 활용할 수 있으며, 확장하여 더 복잡한 웹 애플리케이션으로 발전시킬 수 있습니다. 본 프로젝트는 초보자도 따라할 수 있도록 단계별로 구성되었으며, 각 단계마다 실제 코드와 설명을 제공합니다.

필요한 기술 스택

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

  • Node.js (v14 이상): JavaScript 런타임 환경
  • Express.js: 빠르고 유연한 Node.js 웹 프레임워크
  • MongoDB: NoSQL 데이터베이스
  • Mongoose: MongoDB를 위한 ODM(Object Data Modeling) 라이브러리
  • JWT: 사용자 인증을 위한 JSON Web Token
  • bcrypt: 비밀번호 암호화
  • dotenv: 환경 변수 관리

프로젝트 셋업

먼저 프로젝트 디렉토리를 생성하고 필요한 패키지를 설치합니다. 터미널에서 다음 명령어를 실행하세요:

mkdir blog-project
cd blog-project
npm init -y
npm install express mongoose dotenv bcryptjs jsonwebtoken
npm install --save-dev nodemon

package.json 파일의 scripts 섹션에 다음을 추가합니다:

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

프로젝트 루트에 .env 파일을 생성하고 환경 변수를 설정합니다:

PORT=5000
MONGO_URI=mongodb://localhost:27017/blog-db
JWT_SECRET=your_secret_key_here

단계별 구현 과정

1단계: 서버 설정 및 데이터베이스 연결

MongoDB와 Express.js로 블로그 만들기의 첫 번째 단계는 기본 서버를 구축하는 것입니다. server.js 파일을 생성하고 다음 코드를 작성합니다:

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

dotenv.config();

const app = express();

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

// MongoDB 연결
mongoose.connect(process.env.MONGO_URI, {
  useNewUrlParser: true,
  useUnifiedTopology: true
})
.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단계: 데이터 모델 생성

models 폴더를 생성하고 User.jsPost.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,
    match: [/^\S+@\S+\.\S+$/, '유효한 이메일을 입력해주세요']
  },
  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);
// models/Post.js
const mongoose = require('mongoose');

const postSchema = new mongoose.Schema({
  title: {
    type: String,
    required: [true, '제목을 입력해주세요'],
    trim: true,
    maxlength: 200
  },
  content: {
    type: String,
    required: [true, '내용을 입력해주세요']
  },
  author: {
    type: mongoose.Schema.Types.ObjectId,
    ref: 'User',
    required: true
  },
  tags: [{
    type: String,
    trim: true
  }],
  published: {
    type: Boolean,
    default: false
  },
  views: {
    type: Number,
    default: 0
  },
  createdAt: {
    type: Date,
    default: Date.now
  },
  updatedAt: {
    type: Date,
    default: Date.now
  }
});

// 업데이트 시 updatedAt 자동 갱신
postSchema.pre('save', function(next) {
  this.updatedAt = Date.now();
  next();
});

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

3단계: 인증 미들웨어 구현

middleware 폴더를 생성하고 auth.js 파일을 만듭니다:

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

const protect = async (req, res, next) => {
  let token;

  if (req.headers.authorization && req.headers.authorization.startsWith('Bearer')) {
    token = req.headers.authorization.split(' ')[1];
  }

  if (!token) {
    return res.status(401).json({ message: '인증 토큰이 없습니다' });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = await User.findById(decoded.id).select('-password');
    next();
  } catch (error) {
    return res.status(401).json({ message: '유효하지 않은 토큰입니다' });
  }
};

module.exports = { protect };

4단계: 라우트 및 컨트롤러 구현

routes 폴더와 controllers 폴더를 생성합니다. 먼저 사용자 인증 관련 컨트롤러를 만듭니다:

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

const generateToken = (id) => {
  return jwt.sign({ id }, process.env.JWT_SECRET, { expiresIn: '30d' });
};

exports.register = async (req, res) => {
  try {
    const { username, email, password } = req.body;

    const userExists = await User.findOne({ $or: [{ email }, { username }] });
    if (userExists) {
      return res.status(400).json({ message: '이미 존재하는 사용자입니다' });
    }

    const user = await User.create({ username, email, password });

    res.status(201).json({
      _id: user._id,
      username: user.username,
      email: user.email,
      token: generateToken(user._id)
    });
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
};

exports.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: '이메일 또는 비밀번호가 잘못되었습니다' });
    }

    res.json({
      _id: user._id,
      username: user.username,
      email: user.email,
      token: generateToken(user._id)
    });
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
};

블로그 포스트 CRUD 컨트롤러를 구현합니다:

// controllers/postController.js
const Post = require('../models/Post');

exports.createPost = async (req, res) => {
  try {
    const { title, content, tags, published } = req.body;
    const post = await Post.create({
      title,
      content,
      tags,
      published,
      author: req.user._id
    });

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

exports.getPosts = async (req, res) => {
  try {
    const { page = 1, limit = 10, tag } = req.query;
    const query = tag ? { tags: tag, published: true } : { published: true };

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

    const count = await Post.countDocuments(query);

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

exports.getPost = async (req, res) => {
  try {
    const post = await Post.findById(req.params.id).populate('author', 'username email');
    if (!post) {
      return res.status(404).json({ message: '포스트를 찾을 수 없습니다' });
    }

    post.views += 1;
    await post.save();

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

exports.updatePost = async (req, res) => {
  try {
    const post = await Post.findById(req.params.id);

    if (!post) {
      return res.status(404).json({ message: '포스트를 찾을 수 없습니다' });
    }

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

    const updatedPost = await Post.findByIdAndUpdate(req.params.id, req.body, {
      new: true,
      runValidators: true
    });

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

exports.deletePost = async (req, res) => {
  try {
    const post = await Post.findById(req.params.id);

    if (!post) {
      return res.status(404).json({ message: '포스트를 찾을 수 없습니다' });
    }

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

    await post.deleteOne();
    res.json({ message: '포스트가 삭제되었습니다' });
  } catch (error) {
    res.status(500).json({ message: error.message });
  }
};

라우트 파일을 생성합니다:

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

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

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

router.route('/')
  .get(getPosts)
  .post(protect, createPost);

router.route('/:id')
  .get(getPost)
  .put(protect, updatePost)
  .delete(protect, deletePost);

module.exports = router;

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

// server.js에 추가
const authRoutes = require('./routes/authRoutes');
const postRoutes = require('./routes/postRoutes');

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

테스트 및 배포

API 테스트

Postman이나 Thunder Client를 사용하여 API를 테스트합니다. 주요 엔드포인트는 다음과 같습니다:

  • POST /api/auth/register – 회원가입
  • POST /api/auth/login – 로그인
  • GET /api/posts – 포스트 목록 조회
  • POST /api/posts – 포스트 작성 (인증 필요)
  • GET /api/posts/:id – 특정 포스트 조회
  • PUT /api/posts/:id – 포스트 수정 (인증 필요)
  • DELETE /api/posts/:id – 포스트 삭제 (인증 필요)

배포 준비

MongoDB와 Express.js로 블로그 만들기 프로젝트를 배포하기 위해 다음 단계를 진행합니다:

// .gitignore 파일 생성
node_modules/
.env
*.log

MongoDB Atlas에서 클라우드 데이터베이스를 생성하고, Heroku나 Vercel에 배포할 수 있습니다. 환경 변수는 배포 플랫폼의 설정에서 추가합니다.

마무리 및 확장 아이디어

축하합니다! MongoDB와 Express.js로 블로그 만들기 프로젝트를 완성했습니다. 이제 다음과 같은 기능을 추가하여 프로젝트를 확장할 수 있습니다:

  • 댓글 시스템 구현
  • 좋아요 및 북마크 기능
  • 이미지 업로드 (Multer, Cloudinary)
  • 검색 기능 (ElasticSearch 또는 MongoDB text search)
  • 마크다운 에디터 통합
  • 이메일 인증 및 비밀번호 재설정
  • 소셜 로그인 (OAuth 2.0)
  • 관리자 대시보드

이 프로젝트는 포트폴리오에 추가하기 좋은 실전 예제이며, 실무에서 바로 활용할 수 있는 백엔드 개발 역량을 키울 수 있습니다!

📚 함께 읽으면 좋은 글

1

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

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

2

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

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

3

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

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

4

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

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

5

REST API 서버 구축 단계별 튜토리얼 – 완성까지 한번에!

📂 프로젝트 아이디어
📅 2025. 10. 19.
🎯 REST API 서버 구축 단계별 튜토리얼

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

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

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


📘 페이스북


🐦 트위터


✈️ 텔레그램

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

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

MongoDB와 Express.js로 블로그 만들기에 대한 여러분만의 경험이나 노하우가 있으시나요?

💡
유용한 정보 공유

궁금한 점 질문

🤝
경험담 나누기

👍
의견 표현하기

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

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

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

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

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

💡
최신 트렌드
2025년 기준

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

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

📱 전체 버전 보기