REST API 서버 구축 단계별 튜토리얼 – 완성까지 한번에!
이 REST API 서버 구축 단계별 튜토리얼에서는 Node.js와 Express를 사용하여 실무에서 바로 활용할 수 있는 REST API 서버를 처음부터 끝까지 만들어봅니다. 백엔드 개발의 핵심인 API 서버 구축 방법을 배우고, 데이터베이스 연동부터 인증, 에러 핸들링까지 실전 기능을 모두 구현해봅니다. 이 튜토리얼을 따라하면 포트폴리오에 추가할 수 있는 완성도 높은 프로젝트를 얻을 수 있습니다.
1. 프로젝트 소개 및 목표
🔗 관련 에러 해결 가이드
이 프로젝트는 사용자 관리 기능을 가진 REST API 서버를 구축하는 것을 목표로 합니다. CRUD(Create, Read, Update, Delete) 작업을 모두 지원하며, JWT 기반 인증, 입력 데이터 검증, 에러 핸들링, 로깅 등 실무에서 필수적인 기능들을 포함합니다. 완성된 서버는 MongoDB 데이터베이스와 연동되며, RESTful 설계 원칙을 준수합니다. 이 튜토리얼을 통해 백엔드 개발의 전체 흐름을 이해하고, 실제 서비스 개발에 필요한 기술을 습득할 수 있습니다. 초보자도 따라할 수 있도록 모든 단계를 상세히 설명하며, 각 코드의 의미와 작동 원리를 함께 설명합니다.
2. 필요한 기술 스택
이 REST API 서버 구축 단계별 튜토리얼에서 사용할 기술 스택은 다음과 같습니다:
- Node.js (v18 이상): 서버 런타임 환경
- Express.js: 웹 프레임워크
- MongoDB: NoSQL 데이터베이스
- Mongoose: MongoDB ODM(Object Data Modeling)
- JWT (jsonwebtoken): 사용자 인증
- bcrypt: 비밀번호 암호화
- dotenv: 환경 변수 관리
- express-validator: 입력 데이터 검증
- morgan: HTTP 로깅
- Jest & Supertest: 테스트 프레임워크
모든 패키지는 npm을 통해 설치하며, 개발 환경은 VSCode를 권장합니다.
3. 프로젝트 셋업
먼저 프로젝트 디렉토리를 생성하고 초기 설정을 진행합니다:
# 프로젝트 디렉토리 생성
mkdir rest-api-tutorial
cd rest-api-tutorial
# package.json 생성
npm init -y
# 필요한 패키지 설치
npm install express mongoose jsonwebtoken bcrypt dotenv express-validator morgan cors helmet
# 개발 도구 설치
npm install --save-dev nodemon jest supertest
프로젝트 구조를 다음과 같이 생성합니다:
rest-api-tutorial/
├── src/
│ ├── config/
│ │ └── database.js
│ ├── models/
│ │ └── User.js
│ ├── routes/
│ │ ├── auth.js
│ │ └── users.js
│ ├── middleware/
│ │ ├── auth.js
│ │ └── errorHandler.js
│ ├── controllers/
│ │ ├── authController.js
│ │ └── userController.js
│ └── app.js
├── tests/
├── .env
├── .gitignore
├── server.js
└── package.json
.env 파일을 생성하고 환경 변수를 설정합니다:
PORT=3000
MONGODB_URI=mongodb://localhost:27017/rest-api-tutorial
JWT_SECRET=your_jwt_secret_key_here
NODE_ENV=development
4. 단계별 구현 과정
4.1 데이터베이스 연결 설정
src/config/database.js 파일을 생성하여 MongoDB 연결을 설정합니다:
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 User 모델 생성
src/models/User.js에서 사용자 스키마를 정의합니다:
const mongoose = require('mongoose');
const bcrypt = require('bcrypt');
const userSchema = new mongoose.Schema({
username: {
type: String,
required: [true, '사용자명은 필수입니다'],
unique: true,
trim: true,
minlength: [3, '사용자명은 최소 3자 이상이어야 합니다']
},
email: {
type: String,
required: [true, '이메일은 필수입니다'],
unique: true,
lowercase: true,
match: [/^\S+@\S+\.\S+$/, '올바른 이메일 형식이 아닙니다']
},
password: {
type: String,
required: [true, '비밀번호는 필수입니다'],
minlength: [6, '비밀번호는 최소 6자 이상이어야 합니다']
},
role: {
type: String,
enum: ['user', 'admin'],
default: 'user'
},
createdAt: {
type: Date,
default: Date.now
}
});
// 비밀번호 암호화 미들웨어
userSchema.pre('save', async function(next) {
if (!this.isModified('password')) return next();
try {
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
} catch (error) {
next(error);
}
});
// 비밀번호 비교 메서드
userSchema.methods.comparePassword = async function(candidatePassword) {
return await bcrypt.compare(candidatePassword, this.password);
};
module.exports = mongoose.model('User', userSchema);
4.3 인증 미들웨어 구현
src/middleware/auth.js에서 JWT 인증 미들웨어를 작성합니다:
const jwt = require('jsonwebtoken');
const User = require('../models/User');
const authenticate = 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;
next();
} catch (error) {
res.status(401).json({ error: '유효하지 않은 토큰입니다' });
}
};
const authorize = (...roles) => {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({ error: '권한이 없습니다' });
}
next();
};
};
module.exports = { authenticate, authorize };
4.4 에러 핸들러 미들웨어
src/middleware/errorHandler.js를 생성합니다:
const errorHandler = (err, req, res, next) => {
console.error(err.stack);
if (err.name === 'ValidationError') {
return res.status(400).json({
error: 'Validation Error',
details: Object.values(err.errors).map(e => e.message)
});
}
if (err.code === 11000) {
return res.status(400).json({
error: '중복된 값이 존재합니다',
field: Object.keys(err.keyPattern)[0]
});
}
res.status(err.statusCode || 500).json({
error: err.message || '서버 오류가 발생했습니다'
});
};
module.exports = errorHandler;
4.5 인증 컨트롤러
src/controllers/authController.js에서 회원가입과 로그인 로직을 구현합니다:
const User = require('../models/User');
const jwt = require('jsonwebtoken');
const generateToken = (userId) => {
return jwt.sign({ userId }, process.env.JWT_SECRET, { expiresIn: '7d' });
};
exports.register = async (req, res, next) => {
try {
const { username, email, password } = req.body;
const user = new User({ username, email, password });
await user.save();
const token = generateToken(user._id);
res.status(201).json({
message: '회원가입 성공',
user: {
id: user._id,
username: user.username,
email: user.email,
role: user.role
},
token
});
} catch (error) {
next(error);
}
};
exports.login = async (req, res, next) => {
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 = generateToken(user._id);
res.json({
message: '로그인 성공',
user: {
id: user._id,
username: user.username,
email: user.email,
role: user.role
},
token
});
} catch (error) {
next(error);
}
};
4.6 사용자 컨트롤러
src/controllers/userController.js에서 사용자 CRUD 기능을 구현합니다:
const User = require('../models/User');
exports.getAllUsers = async (req, res, next) => {
try {
const { page = 1, limit = 10 } = req.query;
const users = await User.find()
.select('-password')
.limit(limit * 1)
.skip((page - 1) * limit)
.sort({ createdAt: -1 });
const count = await User.countDocuments();
res.json({
users,
totalPages: Math.ceil(count / limit),
currentPage: page,
total: count
});
} catch (error) {
next(error);
}
};
exports.getUserById = async (req, res, next) => {
try {
const user = await User.findById(req.params.id).select('-password');
if (!user) {
return res.status(404).json({ error: '사용자를 찾을 수 없습니다' });
}
res.json({ user });
} catch (error) {
next(error);
}
};
exports.updateUser = async (req, res, next) => {
try {
const { username, email } = req.body;
const user = await User.findByIdAndUpdate(
req.params.id,
{ username, email },
{ new: true, runValidators: true }
).select('-password');
if (!user) {
return res.status(404).json({ error: '사용자를 찾을 수 없습니다' });
}
res.json({ message: '사용자 정보 수정 성공', user });
} catch (error) {
next(error);
}
};
exports.deleteUser = async (req, res, next) => {
try {
const user = await User.findByIdAndDelete(req.params.id);
if (!user) {
return res.status(404).json({ error: '사용자를 찾을 수 없습니다' });
}
res.json({ message: '사용자 삭제 성공' });
} catch (error) {
next(error);
}
};
4.7 라우트 설정
src/routes/auth.js를 생성합니다:
const express = require('express');
const router = express.Router();
const { body } = require('express-validator');
const authController = require('../controllers/authController');
router.post('/register', [
body('username').trim().isLength({ min: 3 }),
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 6 })
], authController.register);
router.post('/login', [
body('email').isEmail().normalizeEmail(),
body('password').notEmpty()
], authController.login);
module.exports = router;
src/routes/users.js를 생성합니다:
const express = require('express');
const router = express.Router();
const userController = require('../controllers/userController');
const { authenticate, authorize } = require('../middleware/auth');
router.get('/', authenticate, userController.getAllUsers);
router.get('/:id', authenticate, userController.getUserById);
router.put('/:id', authenticate, userController.updateUser);
router.delete('/:id', authenticate, authorize('admin'), userController.deleteUser);
module.exports = router;
4.8 Express 앱 설정
src/app.js에서 Express 애플리케이션을 설정합니다:
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const authRoutes = require('./routes/auth');
const userRoutes = require('./routes/users');
const errorHandler = require('./middleware/errorHandler');
const app = express();
// 미들웨어
app.use(helmet());
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(morgan('dev'));
// 라우트
app.get('/', (req, res) => {
res.json({ message: 'REST API 서버가 정상 작동 중입니다' });
});
app.use('/api/auth', authRoutes);
app.use('/api/users', userRoutes);
// 에러 핸들러
app.use(errorHandler);
module.exports = app;
server.js를 생성하여 서버를 시작합니다:
require('dotenv').config();
const app = require('./src/app');
const connectDB = require('./src/config/database');
const PORT = process.env.PORT || 3000;
connectDB();
app.listen(PORT, () => {
console.log(`서버가 포트 ${PORT}에서 실행 중입니다`);
});
5. 테스트 및 배포
이 REST API 서버 구축 단계별 튜토리얼의 마지막 단계로 테스트를 작성합니다. tests/auth.test.js를 생성합니다:
const request = require('supertest');
const app = require('../src/app');
const mongoose = require('mongoose');
const User = require('../src/models/User');
beforeAll(async () => {
await mongoose.connect(process.env.MONGODB_URI);
});
afterAll(async () => {
await User.deleteMany({});
await mongoose.connection.close();
});
describe('Auth API', () => {
test('POST /api/auth/register - 회원가입 성공', async () => {
const res = await request(app)
.post('/api/auth/register')
.send({
username: 'testuser',
email: '[email protected]',
password: 'password123'
});
expect(res.statusCode).toBe(201);
expect(res.body).toHaveProperty('token');
expect(res.body.user.email).toBe('[email protected]');
});
test('POST /api/auth/login - 로그인 성공', async () => {
const res = await request(app)
.post('/api/auth/login')
.send({
email: '[email protected]',
password: 'password123'
});
expect(res.statusCode).toBe(200);
expect(res.body).toHaveProperty('token');
});
});
package.json에 스크립트를 추가합니다:
{
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"test": "jest --detectOpenHandles"
}
}
테스트를 실행합니다:
npm test
배포는 Heroku, AWS, DigitalOcean 등의 플랫폼을 사용할 수 있습니다. 환경 변수를 반드시 설정하고, MongoDB Atlas 같은 클라우드 데이터베이스를 사용하는 것을 권장합니다.
6. 마무리 및 확장 아이디어
축하합니다! REST API 서버 구축 단계별 튜토리얼을 완료했습니다. 이제 완전히 작동하는 REST API 서버를 갖추게 되었습니다. 추가로 구현해볼 수 있는 확장 아이디어는 다음과 같습니다:
- 이메일 인증: nodemailer를 사용한 회원가입 이메일 인증
- 비밀번호 재설정: 이메일을 통한 비밀번호 복구 기능
- 파일 업로드: multer를 사용한 프로필 이미지 업로드
- 속도 제한: express-rate-limit으로 API 남용 방지
- API 문서화: Swagger를 사용한 자동 API 문서 생성
- Refresh Token: 더 안전한 인증을 위한 리프레시 토큰 구현
- 캐싱: Redis를 사용한 성능 최적화
이 프로젝트를 GitHub에 올리고 포트폴리오에 추가하세요. 실제 서비스 수준의 백엔드 개발 능력을 보여줄 수 있는 훌륭한 프로젝트입니다!
📚 함께 읽으면 좋은 글
REST API 서버 구축 단계별 튜토리얼 – 완성까지 한번에!
📅 2025. 11. 9.
🎯 REST API 서버 구축 단계별 튜토리얼
REST API 서버 구축 단계별 튜토리얼 – 완성까지 한번에!
📅 2025. 11. 5.
🎯 REST API 서버 구축 단계별 튜토리얼
REST API 서버 구축 단계별 튜토리얼 – 완성까지 한번에!
📅 2025. 11. 2.
🎯 REST API 서버 구축 단계별 튜토리얼
REST API 서버 구축 단계별 튜토리얼 – 완성까지 한번에!
📅 2025. 11. 1.
🎯 REST API 서버 구축 단계별 튜토리얼
JWT 인증 시스템 구현하기 – 완성까지 한번에!
📅 2025. 11. 18.
🎯 JWT 인증 시스템 구현하기
💡 위 글들을 통해 더 깊이 있는 정보를 얻어보세요!
📢 이 글이 도움되셨나요? 공유해주세요!
여러분의 공유 한 번이 더 많은 사람들에게 도움이 됩니다 ✨
🔥 공유할 때마다 블로그 성장에 큰 힘이 됩니다! 감사합니다 🙏
💬 여러분의 소중한 의견을 들려주세요!
REST API 서버 구축 단계별 튜토리얼에 대한 여러분만의 경험이나 노하우가 있으시나요?
⭐ 모든 댓글은 24시간 내에 답변드리며, 여러분의 의견이 다른 독자들에게 큰 도움이 됩니다!
🎯 건설적인 의견과 경험 공유를 환영합니다 ✨
🔔 블로그 구독하고 최신 글을 받아보세요!
🌟 프로젝트 아이디어부터 다양한 실생활 정보까지!
매일 새로운 유용한 콘텐츠를 만나보세요 ✨
📧 RSS 구독 | 🔖 북마크 추가 | 📱 모바일 앱 알림 설정
지금 구독하고 놓치는 정보 없이 업데이트 받아보세요!