λͺ©μ°¨
π’ Node.js λ°±μλ μ€μ
Phaser Baduk Metaverse νλ‘μ νΈμ Node.js
λ°±μλ μ€μ κ³Ό ꡬμ±μ λν΄ μμΈν μ€λͺ
ν©λλ€.
1. Node.jsλ 무μμΈκ°μ?
Node.js
λ JavaScript
λ₯Ό μλ²μμ μ€νν μ μκ² ν΄μ£Όλ λ°νμ νκ²½μ
λλ€. μΉ λΈλΌμ°μ μμλ§ μ€νλλ JavaScript
λ₯Ό μλ²μμλ μ€νν μ μκ² ν΄μ€λλ€.
μ£Όμ νΉμ§:
JavaScript
λ‘ μλ²λ₯Ό λ§λ€ μ μμ΅λλ€.- λΉλκΈ° μ²λ¦¬μ κ°μ μ΄ μμ΅λλ€.
npm
μ ν΅ν΄ λ€μν ν¨ν€μ§λ₯Ό μ¬μ©ν μ μμ΅λλ€.- λΉ λ₯Έ κ°λ°μ΄ κ°λ₯ν©λλ€.
2. νλ‘μ νΈ κ°μ
μ΄ νλ‘μ νΈλ Node.js
λ₯Ό κΈ°λ°μΌλ‘ νλ©°, μΉ νμ΄μ§λ₯Ό μλ²μμ λ λλ§νκ³ ν΄λΌμ΄μΈνΈμμ μ€μκ° ν΅μ κΈ°λ₯μ μ 곡ν©λλ€.
μ£Όμ κΈ°μ μ€ν:
Express.js
- μΉ νλ μμν¬Socket.IO
- μ€μκ° ν΅μ λΌμ΄λΈλ¬λ¦¬PM2
- νλ‘μΈμ€ κ΄λ¦¬ λꡬ
3. μλ² κ΅¬μ‘° λ° μ£Όμ νμΌ
Phaser Baduk Metaverse νλ‘μ νΈμ ν΅μ¬ μλ² νμΌμΈ server.js
μ ꡬ쑰μ κ° λΆλΆμ μν μ λν΄ μ€λͺ
ν©λλ€.
1) μ΄κΈ° μ€μ λ° λͺ¨λ λΆλ¬μ€κΈ°
μλ² κ΅¬λμ νμν λͺ¨λλ€μ λΆλ¬μ€κ³ , μ ν리μΌμ΄μ μΈμ€ν΄μ€λ₯Ό μ΄κΈ°νν©λλ€.
const express = require('express'); // μΉ μλ² νλ μμν¬ const http = require('http'); // HTTP μλ² λͺ¨λ const socketIo = require('socket.io'); // μ€μκ° μΉ μμΌ λΌμ΄λΈλ¬λ¦¬ const path = require('path'); // νμΌ κ²½λ‘ μ²λ¦¬ μ νΈλ¦¬ν°
κ° λͺ¨λμ μν :
express
- μΉ μλ²λ₯Ό μ½κ² λ§λ€ μ μλ νλ μμν¬http
-HTTP
μλ²λ₯Ό μμ±νλ κΈ°λ³Έ λͺ¨λsocket.io
- μ€μκ° μλ°©ν₯ ν΅μ μ μν λΌμ΄λΈλ¬λ¦¬path
- νμΌ κ²½λ‘λ₯Ό μμ νκ² μ²λ¦¬νλ μ νΈλ¦¬ν°
2) μ ν리μΌμ΄μ μΈμ€ν΄μ€ μμ±
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
μλ²μ μ°κ²°)
3) μ μ νμΌ μλΉ λ° λΌμ°ν
ν΄λΌμ΄μΈνΈ(νλ‘ νΈμλ)μ μ 곡λ μ μ νμΌ(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('/')
- λ£¨νΈ κ²½λ‘λ‘ μ κ·Όνμ λ μ€νλλ ν¨μμ λλ€.
4) Socket.IO μ΄λ²€νΈ μ²λ¦¬
ν΄λΌμ΄μΈνΈμ μλ² κ°μ μ€μκ° ν΅μ μ λ΄λΉνλ 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')
- ν΄λΌμ΄μΈνΈ μ°κ²°μ΄ λμ΄μ§ λ μ€νλ©λλ€.
5) μλ² ν¬νΈ μ€μ λ° μ€ν
νκ²½ λ³μμμ ν¬νΈ λ²νΈλ₯Ό κ°μ Έμ€κ±°λ κΈ°λ³Έκ°(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()
- μλ²λ₯Ό μ§μ λ ν¬νΈμμ μμν©λλ€.- μ½λ°± ν¨μλ μλ² μμ μ±κ³΅ μ μ€νλλ ν¨μμ λλ€.
4. μμ‘΄μ± κ΄λ¦¬ (package.json)
νλ‘μ νΈμ νμν λΌμ΄λΈλ¬λ¦¬ λ° μ€ν¬λ¦½νΈλ₯Ό μ μνλ package.json
νμΌμ μ£Όμ λ΄μ©μ μ€λͺ
ν©λλ€.
1) νλ‘μ νΈ μ 보
ν€ | μ€λͺ |
---|---|
name | νλ‘μ νΈ μ΄λ¦ |
version | νλ‘μ νΈ λ²μ |
description | νλ‘μ νΈμ λν κ°λ΅ν μ€λͺ |
main | νλ‘μ νΈμ μ§μ
μ νμΌ (μ¬κΈ°μλ server.js ) |
2) μ€ν¬λ¦½νΈ
μμ£Ό μ¬μ©λλ λͺ λ Ήμ΄λ₯Ό λ¨μΆνμ¬ μ μν©λλ€.
μ€ν¬λ¦½νΈ | μ€λͺ | λͺ λ Ήμ΄ |
---|---|---|
start | νλ‘λμ νκ²½μμ μλ²λ₯Ό μμν©λλ€. | node server.js |
dev | κ°λ° νκ²½μμ nodemon μ μ¬μ©νμ¬ μλ²λ₯Ό μμν©λλ€. μ½λ λ³κ²½ μ μλ μ¬μμλ©λλ€. | nodemon server.js |
pm2 | PM2 λ₯Ό μ¬μ©νμ¬ μλ²λ₯Ό μμν©λλ€. (νλ‘μΈμ€ κ΄λ¦¬μ©) | pm2 start server.js |
3) μμ‘΄μ± λͺ©λ‘
μμ‘΄μ± | λ²μ | μ€λͺ |
---|---|---|
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 | μμ² μ ν λ―Έλ€μ¨μ΄ |
4) κ°λ° μμ‘΄μ± λͺ©λ‘
μμ‘΄μ± | λ²μ | μ€λͺ |
---|---|---|
nodemon | 2.0.22 | κ°λ° μ μ½λ λ³κ²½ κ°μ§νμ¬ μλ² μλ μ¬μμ |
jest | 29.5.0 | JavaScript ν μ€νΈ νλ μμν¬ |
supertest | 6.3.3 | HTTP ν μ€νΈ λΌμ΄λΈλ¬λ¦¬ |
5. νκ²½ μ€μ
1) νκ²½ λ³μ μ€μ
νλ‘μ νΈ λ£¨νΈμ .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
)
2) νκ²½ λ³μ μ¬μ©λ²
// 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.λ³μλͺ
- νκ²½ λ³μ κ°μ κ°μ Έμ΅λλ€.|| κΈ°λ³Έκ°
- νκ²½ λ³μκ° μμ λ κΈ°λ³Έκ°μ μ¬μ©ν©λλ€.
6. νλ‘μ νΈ κ΅¬μ‘°
κΆμ₯νλ νλ‘μ νΈ ν΄λ ꡬ쑰:
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/
- ν μ€νΈ μ½λλ€
7. κ°λ° νκ²½ μ€μ
1) Node.js μ€μΉ
Windowsμμ Node.js μ€μΉ:
Node.js
곡μ μΉμ¬μ΄νΈ (https://nodejs.org
) λ°©λ¬ΈLTS
λ²μ λ€μ΄λ‘λ λ° μ€μΉ- μ€μΉ μλ£ ν ν°λ―Έλμμ νμΈ:
node --version npm --version
2) νλ‘μ νΈ μ΄κΈ°ν
# μ ν΄λ μμ± λ° μ΄λ 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
3) κΈ°λ³Έ μλ² νμΌ μμ±
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}μμ μ€ν μ€μ λλ€.`); });
4) package.json μ€ν¬λ¦½νΈ μ€μ
{ "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" } }
8. μλ² μ€ν λ° ν μ€νΈ
1) κ°λ° λͺ¨λλ‘ μλ² μ€ν
npm run dev
μ€λͺ :
nodemon
μ μ¬μ©νμ¬ μλ²λ₯Ό μ€νν©λλ€.- μ½λ λ³κ²½ μ μλμΌλ‘ μλ²κ° μ¬μμλ©λλ€.
- κ°λ° μ€μ λ§€μ° μ μ©ν©λλ€.
2) νλ‘λμ λͺ¨λλ‘ μλ² μ€ν
npm start
μ€λͺ :
node
λ₯Ό μ¬μ©νμ¬ μλ²λ₯Ό μ€νν©λλ€.- μ½λ λ³κ²½ μ μλμΌλ‘ μλ²λ₯Ό μ¬μμν΄μΌ ν©λλ€.
- μ€μ μλΉμ€ νκ²½μμ μ¬μ©ν©λλ€.
3) μλ² ν μ€νΈ
λΈλΌμ°μ μμ ν μ€νΈ:
- λΈλΌμ°μ μ΄κΈ°
http://localhost:3000
μ μ- μλ²κ° μ μμ μΌλ‘ μλ΅νλμ§ νμΈ
ν°λ―Έλμμ ν μ€νΈ:
curl http://localhost:3000
9. λλ²κΉ λ° λ‘κΉ
1) μ½μ λ‘κΉ
// κΈ°λ³Έ λ‘κΉ console.log('μΌλ° λ©μμ§'); console.error('μλ¬ λ©μμ§'); console.warn('κ²½κ³ λ©μμ§'); // κ°μ²΄ λ‘κΉ const gameData = { id: 1, name: 'λ°λ κ²μ' }; console.log('κ²μ λ°μ΄ν°:', gameData); // μκ°κ³Ό ν¨κ» λ‘κΉ console.log(`${new Date().toISOString()} - μλ² μμλ¨`);
2) λ‘κΉ λ―Έλ€μ¨μ΄
// μμ² λ‘κΉ λ―Έλ€μ¨μ΄ 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: 'μλ² λ΄λΆ μ€λ₯κ° λ°μνμ΅λλ€.' }); });
3) λλ²κΉ λꡬ
Node.js λ΄μ₯ λλ²κ±° μ¬μ©:
node --inspect server.js
Chrome DevToolsμμ λλ²κΉ :
- Chrome λΈλΌμ°μ μ΄κΈ°
- chrome://inspect μ μ
- βOpen dedicated DevTools for Nodeβ ν΄λ¦
10. μ±λ₯ μ΅μ ν
1) μμΆ μ€μ
const compression = require('compression'); // μλ΅ μμΆ λ―Έλ€μ¨μ΄ app.use(compression());
2) μΊμ± μ€μ
// μ μ νμΌ μΊμ± app.use(express.static(path.join(dirname, 'public'), { maxAge: '1d', // 1μΌκ° μΊμ etag: true // ETag μ¬μ© }));
3) μμ² μ ν
const rateLimit = require('express-rate-limit'); // API μμ² μ ν const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15λΆ max: 100, // IPλΉ μ΅λ 100κ° μμ² message: 'λ무 λ§μ μμ²μ΄ λ°μνμ΅λλ€.' }); app.use('/api/', limiter);
11. 보μ μ€μ
1) Helmet λ―Έλ€μ¨μ΄
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:"] } } }));
2) CORS μ€μ
const cors = require('cors'); // CORS μ€μ app.use(cors({ origin: process.env.NODE_ENV === 'production' ? ['https://yourdomain.com'] : ['http://localhost:3000'], credentials: true }));
3) μ λ ₯ λ°μ΄ν° κ²μ¦
// μ λ ₯ λ°μ΄ν° κ²μ¦ μμ 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: 'κ²μ νμ μ λ¬Έμμ΄μ΄μ΄μΌ ν©λλ€.' }); } // μΆκ° μ²λ¦¬... });
12. λ€μ λ¨κ³
Node.js
κΈ°λ³Έμ λ°°μ λ€λ©΄ λ€μμ νμ΅ν΄λ³΄μΈμ:
Express.js
- μΉ νλ μμν¬ μ¬ν νμ΅Socket.IO
- μ€μκ° ν΅μ ꡬν- λ°μ΄ν°λ² μ΄μ€ μ°λ -
MongoDB
,MySQL
λ± - μΈμ¦ μμ€ν -
JWT
,Passport.js
λ± - ν μ€νΈ μμ± -
Jest
,Mocha
λ±
μΆμ² νμ΅ μμ:
β μ΄ νμ΄μ§λ μλμΌλ‘ μμ±λμμ΅λλ€. β