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와 연동
이 프로젝트는 포트폴리오로 활용하기에 완벽하며, 실무에서 사용되는 기술 스택과 아키텍처 패턴을 익힐 수 있는 훌륭한 학습 자료입니다. 계속해서 기능을 추가하고 개선하면서 자신만의 독특한 블로그 플랫폼을 만들어보세요!
📚 함께 읽으면 좋은 글
MongoDB와 Express.js로 블로그 만들기 – 완성까지 한번에!
📅 2025. 10. 2.
🎯 MongoDB와 Express.js로 블로그 만들기
30분만에 만드는 Todo App 완성 가이드 – 완성까지 한번에!
📅 2025. 10. 10.
🎯 30분만에 만드는 Todo App 완성 가이드
JWT 인증 시스템 구현하기 – 완성까지 한번에!
📅 2025. 10. 9.
🎯 JWT 인증 시스템 구현하기
실시간 채팅 앱 만들기 with Socket.io – 완성까지 한번에!
📅 2025. 10. 9.
🎯 실시간 채팅 앱 만들기 with Socket.io
React + Node.js 풀스택 앱 배포하기 – 완성까지 한번에!
📅 2025. 10. 9.
🎯 React + Node.js 풀스택 앱 배포하기
💡 위 글들을 통해 더 깊이 있는 정보를 얻어보세요!
📢 이 글이 도움되셨나요? 공유해주세요!
여러분의 공유 한 번이 더 많은 사람들에게 도움이 됩니다 ✨
🔥 공유할 때마다 블로그 성장에 큰 힘이 됩니다! 감사합니다 🙏
💬 여러분의 소중한 의견을 들려주세요!
MongoDB와 Express.js로 블로그 만들기 관련해서 궁금한 점이 더 있으시다면 언제든 물어보세요!
⭐ 모든 댓글은 24시간 내에 답변드리며, 여러분의 의견이 다른 독자들에게 큰 도움이 됩니다!
🎯 건설적인 의견과 경험 공유를 환영합니다 ✨
🔔 블로그 구독하고 최신 글을 받아보세요!
🌟 프로젝트 아이디어부터 다양한 실생활 정보까지!
매일 새로운 유용한 콘텐츠를 만나보세요 ✨
📧 RSS 구독 | 🔖 북마크 추가 | 📱 모바일 앱 알림 설정
지금 구독하고 놓치는 정보 없이 업데이트 받아보세요!