====== πŸš€ Express.js μ›Ή ν”„λ ˆμž„μ›Œν¬ ====== Phaser Baduk Metaverse ν”„λ‘œμ νŠΈμ˜ ''Express.js'' μ›Ή ν”„λ ˆμž„μ›Œν¬ κ΅¬ν˜„μ— λŒ€ν•΄ μ„€λͺ…ν•©λ‹ˆλ‹€. ---- ===== 1. Express.jsλž€ λ¬΄μ—‡μΈκ°€μš”? ===== ''Express.js''λŠ” ''Node.js''λ₯Ό μœ„ν•œ λΉ λ₯΄κ³  μœ μ—°ν•œ μ›Ή μ• ν”Œλ¦¬μΌ€μ΄μ…˜ ν”„λ ˆμž„μ›Œν¬μž…λ‹ˆλ‹€. μ›Ή μ„œλ²„λ₯Ό μ‰½κ²Œ λ§Œλ“€ 수 있게 ν•΄μ£ΌλŠ” 도ꡬ라고 μƒκ°ν•˜μ‹œλ©΄ λ©λ‹ˆλ‹€. **μ£Όμš” νŠΉμ§•:** * μ›Ή μ„œλ²„λ₯Ό λΉ λ₯΄κ²Œ λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€. * ''RESTful API''λ₯Ό μ‰½κ²Œ κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€. * 정적 파일(''HTML'', ''CSS'', 이미지 λ“±)을 μ œκ³΅ν•  수 μžˆμŠ΅λ‹ˆλ‹€. * 미듀웨어λ₯Ό 톡해 κΈ°λŠ₯을 ν™•μž₯ν•  수 μžˆμŠ΅λ‹ˆλ‹€. ---- ===== 2. ν”„λ‘œμ νŠΈ ꡬ쑰 μ΄ν•΄ν•˜κΈ° ===== **Express.js ν”„λ‘œμ νŠΈμ˜ κΈ°λ³Έ ꡬ쑰:** * ''server.js'' - 메인 μ„œλ²„ 파일 * ''routes/'' - 라우트(URL 경둜) 관리 폴더 * ''controllers/'' - λΉ„μ¦ˆλ‹ˆμŠ€ 둜직 관리 폴더 * ''middleware/'' - 미듀웨어(쀑간 처리) 폴더 * ''public/'' - ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ μ œκ³΅ν•  νŒŒμΌλ“€ ---- ===== 3. κΈ°λ³Έ μ„œλ²„ μ„€μ • ===== ==== 1) ν•„μš”ν•œ λͺ¨λ“ˆ 뢈러였기 ==== λ¨Όμ € ν•„μš”ν•œ λͺ¨λ“ˆλ“€μ„ λΆˆλŸ¬μ˜΅λ‹ˆλ‹€: const express = require('express'); const path = require('path'); const cors = require('cors'); const helmet = require('helmet'); const compression = require('compression'); const rateLimit = require('express-rate-limit'); **각 λͺ¨λ“ˆμ˜ μ—­ν• :** * ''express'' - μ›Ή μ„œλ²„ ν”„λ ˆμž„μ›Œν¬ * ''path'' - 파일 경둜 처리 * ''cors'' - λ‹€λ₯Έ λ„λ©”μΈμ—μ„œμ˜ μ ‘κ·Ό ν—ˆμš© * ''helmet'' - λ³΄μ•ˆ κ°•ν™” * ''compression'' - 파일 μ••μΆ• * ''rateLimit'' - μš”μ²­ μ œν•œ ==== 2) Express μ•± 생성 ==== const app = express(); const PORT = process.env.PORT || 3000; **μ„€λͺ…:** * ''app'' - Express μ• ν”Œλ¦¬μΌ€μ΄μ…˜ μΈμŠ€ν„΄μŠ€ * ''PORT'' - μ„œλ²„κ°€ 싀행될 포트 번호 (κΈ°λ³Έκ°’: 3000) ==== 3) λ³΄μ•ˆ 미듀웨어 μ„€μ • ==== app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], scriptSrc: ["'self'", "'unsafe-inline'"], imgSrc: ["'self'", "data:", "https:"], connectSrc: ["'self'", "ws:", "wss:"] } } })); **이 μ„€μ •μ˜ λͺ©μ :** * μ›Ήμ‚¬μ΄νŠΈ λ³΄μ•ˆμ„ κ°•ν™”ν•©λ‹ˆλ‹€. * μ•…μ„± 슀크립트 싀행을 λ°©μ§€ν•©λ‹ˆλ‹€. * ν—ˆμš©λœ λ¦¬μ†ŒμŠ€λ§Œ λ‘œλ“œν•  수 있게 ν•©λ‹ˆλ‹€. ==== 4) CORS μ„€μ • ==== app.use(cors({ origin: process.env.NODE_ENV === 'production' ? ['https://yourdomain.com'] : ['http://localhost:3000', 'http://localhost:8080'], credentials: true })); **CORSλž€?** * λ‹€λ₯Έ λ„λ©”μΈμ—μ„œμ˜ 접근을 μ œμ–΄ν•˜λŠ” λ³΄μ•ˆ μ •μ±…μž…λ‹ˆλ‹€. * 개발 ν™˜κ²½μ—μ„œλŠ” ''localhost:3000'', ''localhost:8080''을 ν—ˆμš©ν•©λ‹ˆλ‹€. * 운영 ν™˜κ²½μ—μ„œλŠ” νŠΉμ • 도메인(''https://yourdomain.com'')만 ν—ˆμš©ν•©λ‹ˆλ‹€. ==== 5) μ••μΆ• 및 μš”μ²­ μ œν•œ μ„€μ • ==== // μ••μΆ• 미듀웨어 app.use(compression()); // μš”μ²­ μ œν•œ μ„€μ • const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15λΆ„ max: 100, // IPλ‹Ή μ΅œλŒ€ 100개 μš”μ²­ message: 'λ„ˆλ¬΄ λ§Žμ€ μš”μ²­μ΄ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€. μž μ‹œ ν›„ λ‹€μ‹œ μ‹œλ„ν•΄μ£Όμ„Έμš”.' }); app.use('/api/', limiter); **μ„€λͺ…:** * ''compression'' - 파일 크기λ₯Ό μ€„μ—¬μ„œ 전솑 속도λ₯Ό ν–₯μƒμ‹œν‚΅λ‹ˆλ‹€. * ''rateLimit'' - ν•œ λ²ˆμ— λ„ˆλ¬΄ λ§Žμ€ μš”μ²­μ„ λ³΄λ‚΄λŠ” 것을 λ°©μ§€ν•©λ‹ˆλ‹€. ==== 6) 데이터 νŒŒμ‹± μ„€μ • ==== // JSON νŒŒμ‹± 미듀웨어 app.use(express.json({ limit: '10mb' })); app.use(express.urlencoded({ extended: true, limit: '10mb' })); **μ„€λͺ…:** * ''express.json()'' - ''JSON'' ν˜•νƒœμ˜ 데이터λ₯Ό νŒŒμ‹±ν•©λ‹ˆλ‹€. * ''express.urlencoded()'' - 폼 데이터λ₯Ό νŒŒμ‹±ν•©λ‹ˆλ‹€. * ''limit: '10mb''' - μ΅œλŒ€ ''10mb''κΉŒμ§€ ν—ˆμš©ν•©λ‹ˆλ‹€. ==== 7) 정적 파일 μ„œλΉ™ μ„€μ • ==== // 정적 파일 μ„œλΉ™ app.use(express.static(path.join(__dirname, 'public'), { maxAge: '1d', etag: true })); **μ„€λͺ…:** * ''public'' ν΄λ”μ˜ νŒŒμΌλ“€μ„ μ›Ήμ—μ„œ μ ‘κ·Ό κ°€λŠ₯ν•˜κ²Œ ν•©λ‹ˆλ‹€. * ''maxAge: '1d''' - νŒŒμΌμ„ 1일간 μΊμ‹œν•©λ‹ˆλ‹€. * ''etag: true'' - 파일 λ³€κ²½ 감지λ₯Ό μœ„ν•œ νƒœκ·Έλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€. ==== 8) λ‘œκΉ… 미듀웨어 ==== // λ‘œκΉ… 미듀웨어 app.use((req, res, next) => { console.log(`${new Date().toISOString()} - ${req.method} ${req.url}`); next(); }); **μ„€λͺ…:** * λͺ¨λ“  μš”μ²­μ— λŒ€ν•΄ 둜그λ₯Ό λ‚¨κΉλ‹ˆλ‹€. * μš”μ²­ μ‹œκ°„, 방법(''GET'', ''POST'' λ“±), ''URL''을 κΈ°λ‘ν•©λ‹ˆλ‹€. * 디버깅에 맀우 μœ μš©ν•©λ‹ˆλ‹€. ---- ===== 4. 라우트 μ„€μ • ===== ==== 1) 라우트 파일 뢈러였기 ==== // 라우트 μ„€μ • app.use('/api/games', require('./routes/games')); app.use('/api/users', require('./routes/users')); app.use('/api/statistics', require('./routes/statistics')); **μ„€λͺ…:** * ''/api/games'' - κ²Œμž„ κ΄€λ ¨ ''API'' * ''/api/users'' - μ‚¬μš©μž κ΄€λ ¨ ''API'' * ''/api/statistics'' - 톡계 κ΄€λ ¨ ''API'' ==== 2) 메인 νŽ˜μ΄μ§€ 라우트 ==== // 메인 νŽ˜μ΄μ§€ app.get('/', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); }); **μ„€λͺ…:** * 루트 경둜(''/ '')둜 μ ‘κ·Όν•˜λ©΄ ''index.html'' νŒŒμΌμ„ μ œκ³΅ν•©λ‹ˆλ‹€. * μ›Ήμ‚¬μ΄νŠΈμ˜ 첫 νŽ˜μ΄μ§€μž…λ‹ˆλ‹€. ---- ===== 5. μ—λŸ¬ 처리 ===== ==== 1) 404 μ—λŸ¬ 처리 ==== // 404 μ—λŸ¬ 처리 app.use((req, res) => { res.status(404).json({ error: 'νŽ˜μ΄μ§€λ₯Ό 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.' }); }); **μ„€λͺ…:** * μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” νŽ˜μ΄μ§€μ— μ ‘κ·Όν•  λ•Œ μ‹€ν–‰λ©λ‹ˆλ‹€. * ''404'' μƒνƒœ μ½”λ“œμ™€ μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€. ==== 2) μ„œλ²„ μ—λŸ¬ 처리 ==== // μ—λŸ¬ 핸듀링 미듀웨어 app.use((err, req, res, next) => { console.error('μ„œλ²„ μ—λŸ¬:', err); res.status(500).json({ error: 'μ„œλ²„ λ‚΄λΆ€ 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€.' }); }); **μ„€λͺ…:** * μ„œλ²„μ—μ„œ λ°œμƒν•˜λŠ” λͺ¨λ“  μ—λŸ¬λ₯Ό μ²˜λ¦¬ν•©λ‹ˆλ‹€. * μ—λŸ¬ 둜그λ₯Ό μ½˜μ†”μ— 좜λ ₯ν•©λ‹ˆλ‹€. * ν΄λΌμ΄μ–ΈνŠΈμ—κ²ŒλŠ” 일반적인 μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό μ „μ†‘ν•©λ‹ˆλ‹€. ---- ===== 6. κ²Œμž„ 라우트 예제 ===== ==== 1) κ²Œμž„ 라우트 κΈ°λ³Έ ꡬ쑰 ==== const express = require('express'); const router = express.Router(); const BadukGameController = require('../controllers/BadukGameController'); const auth = require('../middleware/auth'); **μ„€λͺ…:** * ''express.Router()'' - λΌμš°ν„° 객체 생성 * ''BadukGameController'' - κ²Œμž„ κ΄€λ ¨ λΉ„μ¦ˆλ‹ˆμŠ€ 둜직 * ''auth'' - 인증 미듀웨어 ==== 2) κ²Œμž„ λͺ©λ‘ 쑰회 ==== // κ²Œμž„ λͺ©λ‘ 쑰회 router.get('/', async (req, res) => { try { const games = await BadukGameController.getGameList(req.query); res.json({ success: true, data: games, total: games.length }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } }); **μ„€λͺ…:** * ''GET /api/games'' - λͺ¨λ“  κ²Œμž„ λͺ©λ‘μ„ μ‘°νšŒν•©λ‹ˆλ‹€. * ''try-catch'' - μ—λŸ¬ 처리λ₯Ό μœ„ν•œ κ΅¬λ¬Έμž…λ‹ˆλ‹€. * 성곡 μ‹œ κ²Œμž„ λͺ©λ‘κ³Ό 총 개수λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€. * μ‹€νŒ¨ μ‹œ μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€. ==== 3) μƒˆ κ²Œμž„ 생성 ==== // μƒˆ κ²Œμž„ 생성 router.post('/', auth, async (req, res) => { try { const { gameType, settings } = req.body; const game = await BadukGameController.createGame(req.user.id, gameType, settings); res.status(201).json({ success: true, data: game }); } catch (error) { res.status(400).json({ success: false, error: error.message }); } }); **μ„€λͺ…:** * ''POST /api/games'' - μƒˆλ‘œμš΄ κ²Œμž„μ„ μƒμ„±ν•©λ‹ˆλ‹€. * ''auth'' - 인증된 μ‚¬μš©μžλ§Œ μ ‘κ·Ό κ°€λŠ₯ν•©λ‹ˆλ‹€. * ''req.body'' - ν΄λΌμ΄μ–ΈνŠΈκ°€ 보낸 λ°μ΄ν„°μž…λ‹ˆλ‹€. * ''201'' - 생성 성곡 μƒνƒœ μ½”λ“œμž…λ‹ˆλ‹€. ==== 4) νŠΉμ • κ²Œμž„ 쑰회 ==== // νŠΉμ • κ²Œμž„ 쑰회 router.get('/:gameId', async (req, res) => { try { const game = await BadukGameController.getGame(req.params.gameId); if (!game) { return res.status(404).json({ success: false, error: 'κ²Œμž„μ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.' }); } res.json({ success: true, data: game }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } }); **μ„€λͺ…:** * ''GET /api/games/:gameId'' - νŠΉμ • κ²Œμž„ 정보λ₯Ό μ‘°νšŒν•©λ‹ˆλ‹€. * ''req.params.gameId'' - ''URL''μ—μ„œ κ²Œμž„ ''ID''λ₯Ό μΆ”μΆœν•©λ‹ˆλ‹€. * κ²Œμž„μ΄ μ—†μœΌλ©΄ ''404'' μ—λŸ¬λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€. ==== 5) κ²Œμž„ μ°Έκ°€ ==== // κ²Œμž„ μ°Έκ°€ router.post('/:gameId/join', auth, async (req, res) => { try { const { gameId } = req.params; const { playerName } = req.body; const result = await BadukGameController.joinGame(gameId, req.user.id, playerName); res.json({ success: true, data: result }); } catch (error) { res.status(400).json({ success: false, error: error.message }); } }); **μ„€λͺ…:** * ''POST /api/games/:gameId/join'' - νŠΉμ • κ²Œμž„μ— μ°Έκ°€ν•©λ‹ˆλ‹€. * ''req.params.gameId'' - ''URL''μ—μ„œ κ²Œμž„ ''ID''λ₯Ό κ°€μ Έμ˜΅λ‹ˆλ‹€. * ''req.body.playerName'' - μš”μ²­ λ³Έλ¬Έμ—μ„œ ν”Œλ ˆμ΄μ–΄ 이름을 κ°€μ Έμ˜΅λ‹ˆλ‹€. ==== 6) κ²Œμž„ 이동 기둝 ==== // κ²Œμž„ 이동 기둝 router.post('/:gameId/move', auth, async (req, res) => { try { const { gameId } = req.params; const { x, y, player } = req.body; const result = await BadukGameController.makeMove(gameId, req.user.id, x, y, player); res.json({ success: true, data: result }); } catch (error) { res.status(400).json({ success: false, error: error.message }); } }); **μ„€λͺ…:** * ''POST /api/games/:gameId/move'' - κ²Œμž„μ—μ„œ λŒμ„ λ†“μŠ΅λ‹ˆλ‹€. * ''x, y'' - λ°”λ‘‘νŒμ˜ μ’Œν‘œμž…λ‹ˆλ‹€. * ''player'' - ν”Œλ ˆμ΄μ–΄ μ •λ³΄μž…λ‹ˆλ‹€. ---- ===== 7. μ„œλ²„ μ‹€ν–‰ ===== ==== 1) μ„œλ²„ μ‹œμž‘ ==== // μ„œλ²„ μ‹œμž‘ app.listen(PORT, () => { console.log(`μ„œλ²„κ°€ 포트 ${PORT}μ—μ„œ μ‹€ν–‰ μ€‘μž…λ‹ˆλ‹€.`); console.log(`http://localhost:${PORT}μ—μ„œ μ ‘μ†ν•˜μ„Έμš”.`); }); **μ„€λͺ…:** * μ§€μ •λœ ν¬νŠΈμ—μ„œ μ„œλ²„λ₯Ό μ‹œμž‘ν•©λ‹ˆλ‹€. * μ„œλ²„ μ‹œμž‘ μ‹œ μ½˜μ†”μ— λ©”μ‹œμ§€λ₯Ό 좜λ ₯ν•©λ‹ˆλ‹€. ==== 2) λͺ¨λ“ˆ 내보내기 ==== module.exports = app; **μ„€λͺ…:** * λ‹€λ₯Έ νŒŒμΌμ—μ„œ 이 앱을 μ‚¬μš©ν•  수 μžˆλ„λ‘ λ‚΄λ³΄λƒ…λ‹ˆλ‹€. * ν…ŒμŠ€νŠΈλ‚˜ λ‹€λ₯Έ μ„œλ²„μ—μ„œ μ‚¬μš©ν•  λ•Œ ν•„μš”ν•©λ‹ˆλ‹€. ---- ===== 8. μ‹€μŠ΅ 예제 ===== ==== 1) κ°„λ‹¨ν•œ Express μ„œλ²„ λ§Œλ“€κΈ° ==== **1단계: ν”„λ‘œμ νŠΈ μ΄ˆκΈ°ν™”** npm init -y npm install express **2단계: κΈ°λ³Έ μ„œλ²„ 파일 생성 (''app.js'')** const express = require('express'); const app = express(); const PORT = 3000; // JSON νŒŒμ‹± 미듀웨어 app.use(express.json()); // κ°„λ‹¨ν•œ 라우트 app.get('/', (req, res) => { res.json({ message: 'μ•ˆλ…•ν•˜μ„Έμš”! Express μ„œλ²„μž…λ‹ˆλ‹€.' }); }); app.get('/api/users', (req, res) => { res.json([ { id: 1, name: 'κΉ€μ² μˆ˜' }, { id: 2, name: '이영희' } ]); }); // μ„œλ²„ μ‹œμž‘ app.listen(PORT, () => { console.log(`μ„œλ²„κ°€ 포트 ${PORT}μ—μ„œ μ‹€ν–‰ μ€‘μž…λ‹ˆλ‹€.`); }); **3단계: μ„œλ²„ μ‹€ν–‰** node app.js **4단계: ν…ŒμŠ€νŠΈ** * λΈŒλΌμš°μ €μ—μ„œ ''http://localhost:3000'' 접속 * ''http://localhost:3000/api/users'' 접속 ---- ===== 9. μ£Όμ˜μ‚¬ν•­κ³Ό 팁 ===== ==== 1) λ³΄μ•ˆ μ£Όμ˜μ‚¬ν•­ ==== * 항상 ''helmet'' 미듀웨어λ₯Ό μ‚¬μš©ν•˜μ„Έμš”. * λ―Όκ°ν•œ μ •λ³΄λŠ” ν™˜κ²½ λ³€μˆ˜λ‘œ κ΄€λ¦¬ν•˜μ„Έμš”. * μž…λ ₯ 데이터λ₯Ό κ²€μ¦ν•˜μ„Έμš”. * ''HTTPS''λ₯Ό μ‚¬μš©ν•˜μ„Έμš”. ==== 2) μ„±λŠ₯ μ΅œμ ν™” ==== * ''compression'' 미듀웨어λ₯Ό μ‚¬μš©ν•˜μ„Έμš”. * 정적 파일 캐싱을 μ„€μ •ν•˜μ„Έμš”. * λ°μ΄ν„°λ² μ΄μŠ€ μ—°κ²° 풀을 μ‚¬μš©ν•˜μ„Έμš”. * λΆˆν•„μš”ν•œ 미듀웨어λ₯Ό μ œκ±°ν•˜μ„Έμš”. ==== 3) 디버깅 팁 ==== * λ‘œκΉ… 미듀웨어λ₯Ό μ‚¬μš©ν•˜μ„Έμš”. * μ—λŸ¬ 처리 미듀웨어λ₯Ό μΆ”κ°€ν•˜μ„Έμš”. * 개발 ν™˜κ²½μ—μ„œλŠ” μƒμ„Έν•œ μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό ν‘œμ‹œν•˜μ„Έμš”. * ν”„λ‘œλ•μ…˜μ—μ„œλŠ” 일반적인 μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό ν‘œμ‹œν•˜μ„Έμš”. ---- ===== 10. λ‹€μŒ 단계 ===== ''Express.js'' 기본을 λ°°μ› λ‹€λ©΄ λ‹€μŒμ„ ν•™μŠ΅ν•΄λ³΄μ„Έμš”: * **''Socket.IO''** - μ‹€μ‹œκ°„ 톡신 κ΅¬ν˜„ * **λ°μ΄ν„°λ² μ΄μŠ€ 연동** - ''MongoDB'', ''MySQL'' λ“± * **인증 μ‹œμŠ€ν…œ** - ''JWT'', ''Passport.js'' λ“± * **파일 μ—…λ‘œλ“œ** - ''Multer'' 미듀웨어 * **''API'' λ¬Έμ„œν™”** - ''Swagger'' λ“± **μΆ”μ²œ ν•™μŠ΅ μˆœμ„œ:** - [[wiki:it:dream_of_enc:metaverse:nodejs|🟒 Node.js λ°±μ—”λ“œ μ„€μ •]] - [[wiki:it:dream_of_enc:metaverse:socketio|πŸ”Œ Socket.IO μ‹€μ‹œκ°„ 톡신]] - [[wiki:it:dream_of_enc:metaverse:game_logic|🧠 λ°”λ‘‘ κ²Œμž„ 둜직]] --- //이 νŽ˜μ΄μ§€λŠ” μžλ™μœΌλ‘œ μƒμ„±λ˜μ—ˆμŠ΅λ‹ˆλ‹€.// ---