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

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에 올리고 포트폴리오에 추가하세요. 실제 서비스 수준의 백엔드 개발 능력을 보여줄 수 있는 훌륭한 프로젝트입니다!

📚 함께 읽으면 좋은 글

1

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

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

2

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

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

3

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

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

4

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

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

5

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

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

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

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

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

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

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

REST API 서버 구축 단계별 튜토리얼에 대한 여러분만의 경험이나 노하우가 있으시나요?

💡
유용한 정보 공유

궁금한 점 질문

🤝
경험담 나누기

👍
의견 표현하기

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

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

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

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

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

💡
최신 트렌드
2025년 기준

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

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

답글 남기기