Phaser Baduk Metaverse 프로젝트의 Node.js
백엔드 설정과 구성에 대해 자세히 설명합니다.
Node.js
는 JavaScript
를 서버에서 실행할 수 있게 해주는 런타임 환경입니다. 웹 브라우저에서만 실행되던 JavaScript
를 서버에서도 실행할 수 있게 해줍니다.
주요 특징:
JavaScript
로 서버를 만들 수 있습니다.npm
을 통해 다양한 패키지를 사용할 수 있습니다.
이 프로젝트는 Node.js
를 기반으로 하며, 웹 페이지를 서버에서 렌더링하고 클라이언트와의 실시간 통신 기능을 제공합니다.
주요 기술 스택:
Express.js
- 웹 프레임워크Socket.IO
- 실시간 통신 라이브러리PM2
- 프로세스 관리 도구
Phaser Baduk Metaverse 프로젝트의 핵심 서버 파일인 server.js
의 구조와 각 부분의 역할에 대해 설명합니다.
서버 구동에 필요한 모듈들을 불러오고, 애플리케이션 인스턴스를 초기화합니다.
const express = require('express'); // 웹 서버 프레임워크 const http = require('http'); // HTTP 서버 모듈 const socketIo = require('socket.io'); // 실시간 웹 소켓 라이브러리 const path = require('path'); // 파일 경로 처리 유틸리티
각 모듈의 역할:
express
- 웹 서버를 쉽게 만들 수 있는 프레임워크http
- HTTP
서버를 생성하는 기본 모듈socket.io
- 실시간 양방향 통신을 위한 라이브러리path
- 파일 경로를 안전하게 처리하는 유틸리티const app = express(); // Express 애플리케이션 생성 const server = http.createServer(app); // HTTP 서버 생성 (Express 앱을 기반으로) const io = socketIo(server); // Socket.IO 서버 생성 (HTTP 서버에 연결)
설명:
app
- Express
애플리케이션 객체server
- HTTP
서버 객체 (Express
앱을 기반으로 생성)io
- Socket.IO
서버 객체 (HTTP
서버에 연결)
클라이언트(프론트엔드)에 제공될 정적 파일(HTML
, CSS
, JS
, 이미지 등)을 설정하고, 기본 경로에 대한 라우트를 정의합니다.
// 'public' 디렉토리에 있는 정적 파일들을 클라이언트에게 제공 app.use(express.static(path.join(dirname, 'public'))); // 루트 경로('/')로 접근 시 'public' 디렉토리 내의 'index.html' 파일 전송 app.get('/', (req, res) => { res.sendFile(path.join(dirname, 'public', 'index.html')); });
설명:
express.static()
- 정적 파일을 웹에서 접근 가능하게 합니다.path.join(dirname, 'public')
- 현재 디렉토리의 public
폴더 경로입니다.app.get('/')
- 루트 경로로 접근했을 때 실행되는 함수입니다.
클라이언트와 서버 간의 실시간 통신을 담당하는 Socket.IO
이벤트 핸들러를 설정합니다.
// Socket.IO 연결 이벤트 처리 io.on('connection', (socket) => { // 새로운 클라이언트가 연결될 때마다 실행 console.log('새로운 클라이언트 연결:', socket.id); // 'join-game' 이벤트 수신 시 특정 게임방에 클라이언트 조인 socket.on('join-game', (gameId) => { socket.join(gameId); // 해당 gameId를 가진 방에 소켓을 추가 console.log(`플레이어가 게임 ${gameId}에 참가했습니다.`); }); // 클라이언트 연결 해제 시 실행 socket.on('disconnect', () => { console.log('클라이언트 연결 해제:', socket.id); }); });
설명:
io.on('connection')
- 새로운 클라이언트가 연결될 때 실행됩니다.socket.on('join-game')
- 클라이언트가 게임 참가 요청을 보낼 때 실행됩니다.socket.join(gameId)
- 특정 게임방에 소켓을 추가합니다.socket.on('disconnect')
- 클라이언트 연결이 끊어질 때 실행됩니다.환경 변수에서 포트 번호를 가져오거나 기본값(3000)을 사용하여 서버를 지정된 포트에서 실행합니다.
// 환경 변수에서 PORT를 가져오거나 기본값 3000 사용 const PORT = process.env.PORT || 3000; // 서버를 지정된 포트에서 실행하고, 성공 시 콘솔에 메시지 출력 server.listen(PORT, () => { console.log(`서버가 포트 ${PORT}에서 실행 중입니다.`); console.log(`http://localhost:${PORT}에서 접속하세요.`); });
설명:
process.env.PORT
- 환경 변수에서 포트 번호를 가져옵니다.|| 3000
- 환경 변수가 없으면 기본값 3000을 사용합니다.server.listen()
- 서버를 지정된 포트에서 시작합니다.
프로젝트에 필요한 라이브러리 및 스크립트를 정의하는 package.json
파일의 주요 내용을 설명합니다.
키 | 설명 |
---|---|
name | 프로젝트 이름 |
version | 프로젝트 버전 |
description | 프로젝트에 대한 간략한 설명 |
main | 프로젝트의 진입점 파일 (여기서는 server.js ) |
자주 사용되는 명령어를 단축하여 정의합니다.
스크립트 | 설명 | 명령어 |
---|---|---|
start | 프로덕션 환경에서 서버를 시작합니다. | node server.js |
dev | 개발 환경에서 nodemon 을 사용하여 서버를 시작합니다. 코드 변경 시 자동 재시작됩니다. | nodemon server.js |
pm2 | PM2 를 사용하여 서버를 시작합니다. (프로세스 관리용) | pm2 start server.js |
의존성 | 버전 | 설명 |
---|---|---|
express | 4.18.2 | Node.js 웹 애플리케이션 프레임워크 |
socket.io | 4.7.2 | 실시간 양방향 웹 소켓 통신 라이브러리 |
cors | 2.8.5 | 교차 출처 리소스 공유 (CORS) 미들웨어 |
helmet | 7.0.0 | 보안 헤더 설정 미들웨어 |
compression | 1.7.4 | 응답 압축 미들웨어 |
express-rate-limit | 6.7.0 | 요청 제한 미들웨어 |
의존성 | 버전 | 설명 |
---|---|---|
nodemon | 2.0.22 | 개발 시 코드 변경 감지하여 서버 자동 재시작 |
jest | 29.5.0 | JavaScript 테스트 프레임워크 |
supertest | 6.3.3 | HTTP 테스트 라이브러리 |
프로젝트 루트에 .env
파일을 생성하여 환경 변수를 설정합니다:
# 서버 포트 설정 PORT=3000 # 데이터베이스 연결 정보 MONGODB_URI=mongodb://localhost:27017/baduk_metaverse # JWT 시크릿 키 JWT_SECRET=your-secret-key-here # 환경 설정 NODE_ENV=development
주요 환경 변수:
PORT
- 서버가 실행될 포트 번호MONGODB_URI
- MongoDB
데이터베이스 연결 문자열JWT_SECRET
- JWT
토큰 암호화에 사용할 시크릿 키NODE_ENV
- 현재 환경 (development
/production
)// dotenv 패키지를 사용하여 .env 파일 로드 require('dotenv').config(); // 환경 변수 사용 const PORT = process.env.PORT || 3000; const MONGODB_URI = process.env.MONGODB_URI; const JWT_SECRET = process.env.JWT_SECRET;
설명:
require('dotenv').config()
- .env
파일을 로드합니다.process.env.변수명
- 환경 변수 값을 가져옵니다.|| 기본값
- 환경 변수가 없을 때 기본값을 사용합니다.권장하는 프로젝트 폴더 구조:
baduk-metaverse/ ├── server.js # 메인 서버 파일 ├── package.json # 프로젝트 설정 파일 ├── .env # 환경 변수 파일 ├── .gitignore # Git 무시 파일 목록 ├── public/ # 정적 파일 폴더 │ ├── index.html # 메인 HTML 파일 │ ├── css/ # CSS 파일들 │ ├── js/ # 클라이언트 JavaScript 파일들 │ └── images/ # 이미지 파일들 ├── routes/ # 라우트 파일들 │ ├── games.js # 게임 관련 라우트 │ ├── users.js # 사용자 관련 라우트 │ └── statistics.js # 통계 관련 라우트 ├── controllers/ # 컨트롤러 파일들 │ ├── BadukGameController.js │ ├── UserController.js │ └── StatisticsController.js ├── models/ # 데이터 모델 파일들 │ ├── BadukGame.js │ ├── User.js │ └── BadukGameState.js ├── middleware/ # 미들웨어 파일들 │ ├── auth.js # 인증 미들웨어 │ └── errorHandler.js # 에러 처리 미들웨어 ├── utils/ # 유틸리티 파일들 │ ├── BadukMoveValidator.js │ └── helpers.js └── tests/ # 테스트 파일들 ├── server.test.js └── game.test.js
각 폴더의 역할:
public/
- 클라이언트에게 제공할 파일들routes/
- URL
경로별 처리 로직controllers/
- 비즈니스 로직 처리models/
- 데이터 구조 정의middleware/
- 요청 처리 중간 단계utils/
- 공통 유틸리티 함수들tests/
- 테스트 코드들Windows에서 Node.js 설치:
Node.js
공식 웹사이트 (https://nodejs.org
) 방문LTS
버전 다운로드 및 설치node --version npm --version
# 새 폴더 생성 및 이동 mkdir baduk-metaverse cd baduk-metaverse # npm 프로젝트 초기화 npm init -y # 필요한 패키지 설치 npm install express socket.io cors helmet compression express-rate-limit # 개발용 패키지 설치 npm install --save-dev nodemon
server.js
파일을 생성하고 기본 코드를 작성합니다:
const express = require('express'); const http = require('http'); const socketIo = require('socket.io'); const path = require('path'); const app = express(); const server = http.createServer(app); const io = socketIo(server); // 정적 파일 서빙 app.use(express.static(path.join(dirname, 'public'))); // 기본 라우트 app.get('/', (req, res) => { res.sendFile(path.join(dirname, 'public', 'index.html')); }); // Socket.IO 연결 처리 io.on('connection', (socket) => { console.log('새로운 클라이언트 연결:', socket.id); socket.on('disconnect', () => { console.log('클라이언트 연결 해제:', socket.id); }); }); // 서버 시작 const PORT = process.env.PORT || 3000; server.listen(PORT, () => { console.log(`서버가 포트 ${PORT}에서 실행 중입니다.`); });
{ "name": "baduk-metaverse", "version": "1.0.0", "description": "Phaser Baduk Metaverse 프로젝트", "main": "server.js", "scripts": { "start": "node server.js", "dev": "nodemon server.js", "test": "jest" }, "dependencies": { "express": "^4.18.2", "socket.io": "^4.7.2", "cors": "^2.8.5", "helmet": "^7.0.0", "compression": "^1.7.4", "express-rate-limit": "^6.7.0" }, "devDependencies": { "nodemon": "^2.0.22", "jest": "^29.5.0" } }
npm run dev
설명:
nodemon
을 사용하여 서버를 실행합니다.npm start
설명:
node
를 사용하여 서버를 실행합니다.브라우저에서 테스트:
터미널에서 테스트:
curl http://localhost:3000
// 기본 로깅 console.log('일반 메시지'); console.error('에러 메시지'); console.warn('경고 메시지'); // 객체 로깅 const gameData = { id: 1, name: '바둑 게임' }; console.log('게임 데이터:', gameData); // 시간과 함께 로깅 console.log(`${new Date().toISOString()} - 서버 시작됨`);
// 요청 로깅 미들웨어 app.use((req, res, next) => { console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`); next(); }); // 에러 로깅 미들웨어 app.use((err, req, res, next) => { console.error('서버 에러:', err); res.status(500).json({ error: '서버 내부 오류가 발생했습니다.' }); });
Node.js 내장 디버거 사용:
node --inspect server.js
Chrome DevTools에서 디버깅:
const compression = require('compression'); // 응답 압축 미들웨어 app.use(compression());
// 정적 파일 캐싱 app.use(express.static(path.join(dirname, 'public'), { maxAge: '1d', // 1일간 캐시 etag: true // ETag 사용 }));
const rateLimit = require('express-rate-limit'); // API 요청 제한 const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15분 max: 100, // IP당 최대 100개 요청 message: '너무 많은 요청이 발생했습니다.' }); app.use('/api/', limiter);
const helmet = require('helmet'); // 보안 헤더 설정 app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], scriptSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", "data:", "https:"], connectSrc: ["'self'", "ws:", "wss:"] } } }));
const cors = require('cors'); // CORS 설정 app.use(cors({ origin: process.env.NODE_ENV === 'production' ? ['https://yourdomain.com'] : ['http://localhost:3000'], credentials: true }));
// 입력 데이터 검증 예제 app.post('/api/games', (req, res) => { const { gameType, settings } = req.body; // 필수 필드 검증 if (!gameType) { return res.status(400).json({ error: '게임 타입이 필요합니다.' }); } // 데이터 타입 검증 if (typeof gameType !== 'string') { return res.status(400).json({ error: '게임 타입은 문자열이어야 합니다.' }); } // 추가 처리... });
Node.js
기본을 배웠다면 다음을 학습해보세요:
Express.js
- 웹 프레임워크 심화 학습Socket.IO
- 실시간 통신 구현MongoDB
, MySQL
등JWT
, Passport.js
등Jest
, Mocha
등추천 학습 순서:
— 이 페이지는 자동으로 생성되었습니다. —