๋ชฉ์ฐจ
๐ 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
์ ์
9. ์ฃผ์์ฌํญ๊ณผ ํ
1) ๋ณด์ ์ฃผ์์ฌํญ
- ํญ์
helmet
๋ฏธ๋ค์จ์ด๋ฅผ ์ฌ์ฉํ์ธ์. - ๋ฏผ๊ฐํ ์ ๋ณด๋ ํ๊ฒฝ ๋ณ์๋ก ๊ด๋ฆฌํ์ธ์.
- ์ ๋ ฅ ๋ฐ์ดํฐ๋ฅผ ๊ฒ์ฆํ์ธ์.
HTTPS
๋ฅผ ์ฌ์ฉํ์ธ์.
2) ์ฑ๋ฅ ์ต์ ํ
compression
๋ฏธ๋ค์จ์ด๋ฅผ ์ฌ์ฉํ์ธ์.- ์ ์ ํ์ผ ์บ์ฑ์ ์ค์ ํ์ธ์.
- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๊ฒฐ ํ์ ์ฌ์ฉํ์ธ์.
- ๋ถํ์ํ ๋ฏธ๋ค์จ์ด๋ฅผ ์ ๊ฑฐํ์ธ์.
3) ๋๋ฒ๊น ํ
- ๋ก๊น ๋ฏธ๋ค์จ์ด๋ฅผ ์ฌ์ฉํ์ธ์.
- ์๋ฌ ์ฒ๋ฆฌ ๋ฏธ๋ค์จ์ด๋ฅผ ์ถ๊ฐํ์ธ์.
- ๊ฐ๋ฐ ํ๊ฒฝ์์๋ ์์ธํ ์๋ฌ ๋ฉ์์ง๋ฅผ ํ์ํ์ธ์.
- ํ๋ก๋์ ์์๋ ์ผ๋ฐ์ ์ธ ์๋ฌ ๋ฉ์์ง๋ฅผ ํ์ํ์ธ์.
10. ๋ค์ ๋จ๊ณ
Express.js
๊ธฐ๋ณธ์ ๋ฐฐ์ ๋ค๋ฉด ๋ค์์ ํ์ตํด๋ณด์ธ์:
Socket.IO
- ์ค์๊ฐ ํต์ ๊ตฌํ- ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฐ๋ -
MongoDB
,MySQL
๋ฑ - ์ธ์ฆ ์์คํ -
JWT
,Passport.js
๋ฑ - ํ์ผ ์ ๋ก๋ -
Multer
๋ฏธ๋ค์จ์ด API
๋ฌธ์ํ -Swagger
๋ฑ
์ถ์ฒ ํ์ต ์์:
โ ์ด ํ์ด์ง๋ ์๋์ผ๋ก ์์ฑ๋์์ต๋๋ค. โ