๋ฌธ์์ ์ด์ ํ์ ๋๋ค!
๋ชฉ์ฐจ
๐ 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๋?
- ๋ค๋ฅธ ๋๋ฉ์ธ์์์ ์ ๊ทผ์ ์ ์ดํ๋ ๋ณด์ ์ ์ฑ
- ๊ฐ๋ฐ ํ๊ฒฝ์์๋ ๋ก์ปฌํธ์คํธ ํ์ฉ
- ์ด์ ํ๊ฒฝ์์๋ ํน์ ๋๋ฉ์ธ๋ง ํ์ฉ
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๋จ๊ณ: ํ๋ก์ ํธ ์ด๊ธฐํ
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๋จ๊ณ: ์๋ฒ ์คํ
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 ๋ฑ
์ถ์ฒ ํ์ต ์์:
1. [[wiki:it:dream_of_enc:metaverse:nodejs|๐ข Node.js ๋ฐฑ์๋ ์ค์ ]] 2. [[wiki:it:dream_of_enc:metaverse:socketio|๐ Socket.IO ์ค์๊ฐ ํต์ ]] 3. [[wiki:it:dream_of_enc:metaverse:game_logic|๐ง ๋ฐ๋ ๊ฒ์ ๋ก์ง]]
โ ์ด ํ์ด์ง๋ ์๋์ผ๋ก ์์ฑ๋์์ต๋๋ค.