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

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

REST API 서버 구축 단계별 튜토리얼은 Node.js와 Express를 사용하여 실전에서 활용 가능한 RESTful API 서버를 처음부터 끝까지 구축하는 과정을 다룹니다. 백엔드 개발의 핵심인 REST API 서버를 직접 만들어보면서 HTTP 메서드, 라우팅, 데이터베이스 연동, 인증 및 에러 처리까지 모든 과정을 실습할 수 있습니다. 이 튜토리얼을 완료하면 포트폴리오에 추가할 수 있는 완성도 높은 프로젝트를 갖게 될 것입니다.

1. 프로젝트 소개 및 목표

이번 프로젝트에서는 사용자 관리 시스템을 위한 REST API 서버를 구축합니다. 주요 목표는 다음과 같습니다:

  • CRUD 작업 구현: 사용자 생성(Create), 조회(Read), 수정(Update), 삭제(Delete) 기능
  • RESTful 설계 원칙 적용: 표준 HTTP 메서드와 상태 코드 활용
  • 데이터베이스 연동: MongoDB를 사용한 영구 데이터 저장
  • 인증 시스템: JWT 기반 사용자 인증 구현
  • 에러 핸들링: 체계적인 오류 처리 및 검증

이 프로젝트는 실무에서 요구되는 백엔드 개발 역량을 키우는 데 최적화되어 있으며, 프론트엔드 애플리케이션과 연동할 수 있는 완전한 API 서버를 완성하게 됩니다.

2. 필요한 기술 스택

REST API 서버 구축 단계별 튜토리얼에서 사용할 기술 스택은 다음과 같습니다:

  • Node.js (v18 이상): JavaScript 런타임 환경
  • Express.js: 웹 프레임워크로 라우팅과 미들웨어 처리
  • MongoDB: NoSQL 데이터베이스로 사용자 데이터 저장
  • Mongoose: MongoDB ODM(Object Data Modeling) 라이브러리
  • JWT (jsonwebtoken): 토큰 기반 인증
  • bcrypt: 비밀번호 암호화
  • dotenv: 환경 변수 관리
  • express-validator: 입력 데이터 검증
  • Jest & Supertest: API 테스트 도구

이러한 기술 스택은 현대적인 웹 개발에서 널리 사용되며, 학습 곡선이 완만하여 초보자도 쉽게 따라올 수 있습니다.

3. 프로젝트 셋업

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

// 1. 프로젝트 디렉토리 생성 및 초기화
mkdir rest-api-tutorial
cd rest-api-tutorial
npm init -y

// 2. 필요한 패키지 설치
npm install express mongoose jsonwebtoken bcryptjs dotenv express-validator cors
npm install --save-dev nodemon jest supertest

// 3. 프로젝트 구조 생성
mkdir src
mkdir src/models src/routes src/controllers src/middleware src/config
touch src/server.js src/app.js .env .gitignore

package.json 스크립트를 설정합니다:

{
  "scripts": {
    "start": "node src/server.js",
    "dev": "nodemon src/server.js",
    "test": "jest --watchAll --verbose"
  }
}

.env 파일에 환경 변수를 설정합니다:

PORT=3000
MONGODB_URI=mongodb://localhost:27017/rest-api-tutorial
JWT_SECRET=your_jwt_secret_key_here
NODE_ENV=development

.gitignore 파일을 작성하여 민감한 정보를 보호합니다:

node_modules/
.env
*.log

4. 단계별 구현 과정

Step 1: 기본 서버 설정

src/app.js 파일을 생성하여 Express 애플리케이션을 설정합니다:

const express = require('express');
const cors = require('cors');
require('dotenv').config();

const app = express();

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

// 헬스 체크 라우트
app.get('/health', (req, res) => {
  res.status(200).json({ status: 'OK', message: 'Server is running' });
});

// 에러 핸들러
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(err.status || 500).json({
    error: {
      message: err.message || 'Internal Server Error'
    }
  });
});

module.exports = app;

src/server.js 파일을 생성하여 서버를 시작합니다:

const app = require('./app');
const connectDB = require('./config/database');

const PORT = process.env.PORT || 3000;

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

// 서버 시작
app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

Step 2: 데이터베이스 연결 설정

src/config/database.js 파일을 생성합니다:

const mongoose = require('mongoose');

const connectDB = async () => {
  try {
    await mongoose.connect(process.env.MONGODB_URI, {
      useNewUrlParser: true,
      useUnifiedTopology: true
    });
    console.log('MongoDB connected successfully');
  } catch (error) {
    console.error('MongoDB connection failed:', error.message);
    process.exit(1);
  }
};

module.exports = connectDB;

Step 3: User 모델 생성

src/models/User.js 파일을 생성하여 사용자 스키마를 정의합니다:

const mongoose = require('mongoose');
const bcrypt = require('bcryptjs');

const userSchema = new mongoose.Schema({
  name: {
    type: String,
    required: [true, 'Name is required'],
    trim: true,
    minlength: [2, 'Name must be at least 2 characters'],
    maxlength: [50, 'Name cannot exceed 50 characters']
  },
  email: {
    type: String,
    required: [true, 'Email is required'],
    unique: true,
    lowercase: true,
    trim: true,
    match: [/^\S+@\S+\.\S+$/, 'Please enter a valid email']
  },
  password: {
    type: String,
    required: [true, 'Password is required'],
    minlength: [6, 'Password must be at least 6 characters'],
    select: false
  },
  role: {
    type: String,
    enum: ['user', 'admin'],
    default: 'user'
  }
}, {
  timestamps: true
});

// 비밀번호 암호화 미들웨어
userSchema.pre('save', async function(next) {
  if (!this.isModified('password')) return next();
  
  const salt = await bcrypt.genSalt(10);
  this.password = await bcrypt.hash(this.password, salt);
  next();
});

// 비밀번호 검증 메서드
userSchema.methods.comparePassword = async function(candidatePassword) {
  return await bcrypt.compare(candidatePassword, this.password);
};

module.exports = mongoose.model('User', userSchema);

Step 4: 인증 미들웨어 구현

src/middleware/auth.js 파일을 생성합니다:

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

exports.protect = async (req, res, next) => {
  try {
    let token;

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

    if (!token) {
      return res.status(401).json({ error: 'Not authorized to access this route' });
    }

    // 토큰 검증
    const decoded = jwt.verify(token, process.env.JWT_SECRET);

    // 사용자 확인
    req.user = await User.findById(decoded.id);

    if (!req.user) {
      return res.status(401).json({ error: 'User not found' });
    }

    next();
  } catch (error) {
    return res.status(401).json({ error: 'Not authorized to access this route' });
  }
};

exports.authorize = (...roles) => {
  return (req, res, next) => {
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ 
        error: 'User role is not authorized to access this route' 
      });
    }
    next();
  };
};

Step 5: 컨트롤러 구현

src/controllers/authController.js 파일을 생성합니다:

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

// JWT 토큰 생성 함수
const generateToken = (id) => {
  return jwt.sign({ id }, process.env.JWT_SECRET, {
    expiresIn: '30d'
  });
};

// 회원가입
exports.register = async (req, res) => {
  try {
    const { name, email, password } = req.body;

    // 이메일 중복 확인
    const existingUser = await User.findOne({ email });
    if (existingUser) {
      return res.status(400).json({ error: 'Email already registered' });
    }

    // 사용자 생성
    const user = await User.create({ name, email, password });

    // 토큰 생성
    const token = generateToken(user._id);

    res.status(201).json({
      success: true,
      data: {
        id: user._id,
        name: user.name,
        email: user.email,
        role: user.role
      },
      token
    });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
};

// 로그인
exports.login = async (req, res) => {
  try {
    const { email, password } = req.body;

    // 이메일과 비밀번호 확인
    if (!email || !password) {
      return res.status(400).json({ error: 'Please provide email and password' });
    }

    // 사용자 조회 (비밀번호 포함)
    const user = await User.findOne({ email }).select('+password');

    if (!user) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }

    // 비밀번호 확인
    const isPasswordValid = await user.comparePassword(password);

    if (!isPasswordValid) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }

    // 토큰 생성
    const token = generateToken(user._id);

    res.status(200).json({
      success: true,
      data: {
        id: user._id,
        name: user.name,
        email: user.email,
        role: user.role
      },
      token
    });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
};

src/controllers/userController.js 파일을 생성합니다:

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

// 모든 사용자 조회
exports.getUsers = async (req, res) => {
  try {
    const users = await User.find();
    res.status(200).json({
      success: true,
      count: users.length,
      data: users
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
};

// 특정 사용자 조회
exports.getUser = async (req, res) => {
  try {
    const user = await User.findById(req.params.id);
    
    if (!user) {
      return res.status(404).json({ error: 'User not found' });
    }

    res.status(200).json({
      success: true,
      data: user
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
};

// 사용자 정보 수정
exports.updateUser = async (req, res) => {
  try {
    const { name, email } = req.body;

    const user = await User.findByIdAndUpdate(
      req.params.id,
      { name, email },
      { new: true, runValidators: true }
    );

    if (!user) {
      return res.status(404).json({ error: 'User not found' });
    }

    res.status(200).json({
      success: true,
      data: user
    });
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
};

// 사용자 삭제
exports.deleteUser = async (req, res) => {
  try {
    const user = await User.findByIdAndDelete(req.params.id);

    if (!user) {
      return res.status(404).json({ error: 'User not found' });
    }

    res.status(200).json({
      success: true,
      data: {}
    });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
};

Step 6: 라우트 설정

src/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;

src/routes/userRoutes.js 파일을 생성합니다:

const express = require('express');
const {
  getUsers,
  getUser,
  updateUser,
  deleteUser
} = require('../controllers/userController');
const { protect, authorize } = require('../middleware/auth');

const router = express.Router();

router.get('/', protect, getUsers);
router.get('/:id', protect, getUser);
router.put('/:id', protect, updateUser);
router.delete('/:id', protect, authorize('admin'), deleteUser);

module.exports = router;

src/app.js에 라우트를 추가합니다:

// 라우트 임포트
const authRoutes = require('./routes/authRoutes');
const userRoutes = require('./routes/userRoutes');

// 라우트 등록
app.use('/api/v1/auth', authRoutes);
app.use('/api/v1/users', userRoutes);

5. 테스트 및 배포

API 테스트

tests/auth.test.js 파일을 생성하여 테스트를 작성합니다:

const request = require('supertest');
const app = require('../src/app');
const User = require('../src/models/User');

describe('Auth API', () => {
  beforeEach(async () => {
    await User.deleteMany({});
  });

  describe('POST /api/v1/auth/register', () => {
    it('should register a new user', async () => {
      const res = await request(app)
        .post('/api/v1/auth/register')
        .send({
          name: 'Test User',
          email: '[email protected]',
          password: 'password123'
        });

      expect(res.statusCode).toBe(201);
      expect(res.body).toHaveProperty('token');
      expect(res.body.data.email).toBe('[email protected]');
    });
  });

  describe('POST /api/v1/auth/login', () => {
    it('should login existing user', async () => {
      // 사용자 생성
      await User.create({
        name: 'Test User',
        email: '[email protected]',
        password: 'password123'
      });

      const res = await request(app)
        .post('/api/v1/auth/login')
        .send({
          email: '[email protected]',
          password: 'password123'
        });

      expect(res.statusCode).toBe(200);
      expect(res.body).toHaveProperty('token');
    });
  });
});

Postman을 사용한 수동 테스트

다음 엔드포인트를 테스트합니다:

  • POST /api/v1/auth/register – 회원가입
  • POST /api/v1/auth/login – 로그인
  • GET /api/v1/users – 사용자 목록 조회 (인증 필요)
  • GET /api/v1/users/:id – 특정 사용자 조회 (인증 필요)
  • PUT /api/v1/users/:id – 사용자 정보 수정 (인증 필요)
  • DELETE /api/v1/users/:id – 사용자 삭제 (관리자 권한 필요)

배포

Heroku나 AWS에 배포하기 전에 다음을 확인합니다:

// package.json에 엔진 버전 명시
"engines": {
  "node": ">=18.0.0",
  "npm": ">=9.0.0"
}

환경 변수를 프로덕션 서버에 설정하고, MongoDB Atlas를 사용하여 클라우드 데이터베이스를 연결합니다.

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

REST API 서버 구축 단계별 튜토리얼을 통해 완전한 기능을 갖춘 RESTful API 서버를 구축했습니다. 추가로 다음과 같은 기능을 확장할 수 있습니다:

  • 페이지네이션: 대량의 데이터를 효율적으로 조회
  • 파일 업로드: Multer를 사용한 프로필 이미지 업로드
  • 이메일 인증: Nodemailer를 활용한 회원가입 인증
  • 비밀번호 재설정: 이메일을 통한 비밀번호 복구 기능
  • Rate Limiting: express-rate-limit으로 API 호출 제한
  • API 문서화: Swagger를 사용한 자동 문서 생성
  • 로깅 시스템: Winston이나 Morgan을 활용한 로그 관리
  • 캐싱: Redis를 사용한 성능 최적화

이 프로젝트는 포트폴리오에 추가하거나 실제 서비스의 기반으로 활용할 수 있습니다. REST API 서버 구축 단계별 튜토리얼을 완료하셨다면, 프론트엔드 애플리케이션과 연동하여 풀스택 프로젝트로 발전시켜 보세요!

📚 함께 읽으면 좋은 글

1

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

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

2

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

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

3

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

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

4

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

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

5

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

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

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

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

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

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

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

REST API 서버 구축 단계별 튜토리얼 관련해서 궁금한 점이 더 있으시다면 언제든 물어보세요!

💡
유용한 정보 공유

궁금한 점 질문

🤝
경험담 나누기

👍
의견 표현하기

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

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

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

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

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

💡
최신 트렌드
2025년 기준

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

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

답글 남기기