์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ JavaScript ํ”„๋ก์‹œ ์„œ๋ฒ„๋กœ CORS ์ด์Šˆ ์™„๋ฒฝ ํ•ด๊ฒฐํ•˜๊ธฐ

๐Ÿš€ JavaScript ํ”„๋ก์‹œ ์„œ๋ฒ„๋กœ CORS ์ด์Šˆ ์™„๋ฒฝ ํ•ด๊ฒฐํ•˜๊ธฐ

์›น ๊ฐœ๋ฐœ์ž๋ผ๋ฉด ๋ฐ˜๋“œ์‹œ ์•Œ์•„์•ผ ํ•  CORS ๋ฌธ์ œ์™€ ํ”„๋ก์‹œ ์„œ๋ฒ„ ํ™œ์šฉ๋ฒ•

๐ŸŽฏ ์™œ CORS๋Š” ์šฐ๋ฆฌ๋ฅผ ๊ดด๋กญํž๊นŒ?

์•ˆ๋…•! ํ˜น์‹œ ์›น ๊ฐœ๋ฐœํ•˜๋‹ค๊ฐ€ ์ฝ˜์†”์ฐฝ์— ๋นจ๊ฐ„ ๊ธ€์”จ๋กœ "Access to fetch at... has been blocked by CORS policy"๋ผ๋Š” ๋ฌด์‹œ๋ฌด์‹œํ•œ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ๋ณธ ์  ์žˆ์ง€? ๐Ÿ˜ฑ ์ฒ˜์Œ ๋ณด๋Š” ์ˆœ๊ฐ„ ์ •๋ง ๋‹นํ™ฉ์Šค๋Ÿฝ์ง€. ๋ถ„๋ช… ์ฝ”๋“œ๋Š” ์ œ๋Œ€๋กœ ์งฐ๋Š”๋ฐ, API ํ˜ธ์ถœ๋งŒ ํ•˜๋ฉด ๋ง‰ํžˆ๋Š” ์ด ์ƒํ™ฉ!

์‚ฌ์‹ค CORS(Cross-Origin Resource Sharing) ๋ฌธ์ œ๋Š” ์›น ๊ฐœ๋ฐœ์ž๋ผ๋ฉด ๋ˆ„๊ตฌ๋‚˜ ํ•œ ๋ฒˆ์ฏค ๋งˆ์ฃผ์น˜๋Š” ํ†ต๊ณผ์˜๋ก€ ๊ฐ™์€ ๊ฑฐ์•ผ. ํŠนํžˆ ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์„ ํ•˜๋‹ค ๋ณด๋ฉด ์™ธ๋ถ€ API๋ฅผ ํ˜ธ์ถœํ•  ์ผ์ด ์ •๋ง ๋งŽ์€๋ฐ, ์ด๋•Œ๋งˆ๋‹ค CORS ์—๋Ÿฌ๊ฐ€ ํŠ€์–ด๋‚˜์™€์„œ ๊ฐœ๋ฐœ ์ง„ํ–‰์„ ๋ง‰์•„๋ฒ„๋ฆฌ๊ฑฐ๋“ . ๐Ÿ˜ค

์˜ค๋Š˜์€ ์ด ๊ณจ์น˜ ์•„ํ”ˆ CORS ๋ฌธ์ œ๋ฅผ JavaScript ํ”„๋ก์‹œ ์„œ๋ฒ„๋กœ ๊น”๋”ํ•˜๊ฒŒ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์นœ๊ตฌ์ฒ˜๋Ÿผ ํŽธํ•˜๊ฒŒ ์•Œ๋ ค์ค„๊ฒŒ! ์žฌ๋Šฅ๋„ท์—์„œ ์›น ๊ฐœ๋ฐœ ๊ด€๋ จ ์žฌ๋Šฅ์„ ๊ณต์œ ํ•˜๊ฑฐ๋‚˜ ๋ฐฐ์šฐ๋ ค๋Š” ๋ถ„๋“ค์—๊ฒŒ๋„ ์ •๋ง ์œ ์šฉํ•œ ๋‚ด์šฉ์ด ๋  ๊ฑฐ์•ผ. ๐Ÿ’ช

CORS ๋ฌธ์ œ ๋ฐœ์ƒ ๊ตฌ์กฐ ๋ธŒ๋ผ์šฐ์ € localhost:3000 API ์„œ๋ฒ„ api.example.com โŒ CORS ์—๋Ÿฌ ๋ฐœ์ƒ! ํ”„๋ก์‹œ ์„œ๋ฒ„ ํ•ด๊ฒฐ์‚ฌ ๋“ฑ์žฅ! ๐ŸŽ‰ โœ“ ์•ˆ์ „ํ•œ ์š”์ฒญ โœ“ ์ •์ƒ ์‘๋‹ต

๐Ÿค” CORS๊ฐ€ ์ •ํ™•ํžˆ ๋ญ˜๊นŒ?

CORS๋ฅผ ์ดํ•ดํ•˜๋ ค๋ฉด ๋จผ์ € ๋™์ผ ์ถœ์ฒ˜ ์ •์ฑ…(Same-Origin Policy)์„ ์•Œ์•„์•ผ ํ•ด. ์ด๊ฑด ์›น ๋ธŒ๋ผ์šฐ์ €์˜ ๋ณด์•ˆ ๋ฉ”์ปค๋‹ˆ์ฆ˜์ธ๋ฐ, ์‰ฝ๊ฒŒ ๋งํ•˜๋ฉด "๋‹ค๋ฅธ ์ถœ์ฒ˜์˜ ๋ฆฌ์†Œ์Šค๋Š” ํ•จ๋ถ€๋กœ ๊ฐ€์ ธ์˜ค์ง€ ๋งˆ!"๋ผ๋Š” ๊ทœ์น™์ด์•ผ. ๐Ÿ›ก๏ธ

์—ฌ๊ธฐ์„œ '์ถœ์ฒ˜(Origin)'๋Š” ํ”„๋กœํ† ์ฝœ, ๋„๋ฉ”์ธ, ํฌํŠธ ๋ฒˆํ˜ธ์˜ ์กฐํ•ฉ์„ ๋งํ•ด. ์˜ˆ๋ฅผ ๋“ค์–ด๋ณผ๊ฒŒ:

โœ… ๊ฐ™์€ ์ถœ์ฒ˜:
https://www.example.com/page1
https://www.example.com/page2

โŒ ๋‹ค๋ฅธ ์ถœ์ฒ˜:
http://www.example.com (ํ”„๋กœํ† ์ฝœ ๋‹ค๋ฆ„)
https://api.example.com (์„œ๋ธŒ๋„๋ฉ”์ธ ๋‹ค๋ฆ„)
https://www.example.com:8080 (ํฌํŠธ ๋‹ค๋ฆ„)

๋ธŒ๋ผ์šฐ์ €๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ ๋‹ค๋ฅธ ์ถœ์ฒ˜๋กœ์˜ ์š”์ฒญ์„ ์ฐจ๋‹จํ•ด. ์™œ๋ƒํ•˜๋ฉด ์•…์˜์ ์ธ ์›น์‚ฌ์ดํŠธ๊ฐ€ ์‚ฌ์šฉ์ž ๋ชฐ๋ž˜ ๋‹ค๋ฅธ ์‚ฌ์ดํŠธ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ›”์ณ๊ฐˆ ์ˆ˜ ์žˆ๊ฑฐ๋“ . ์ด๊ฒŒ ๋ฐ”๋กœ ๋ณด์•ˆ์„ ์œ„ํ•œ ์กฐ์น˜์•ผ! ๐Ÿ”’

ํ•˜์ง€๋งŒ ํ˜„๋Œ€ ์›น ๊ฐœ๋ฐœ์—์„œ๋Š” ๋‹ค๋ฅธ ์ถœ์ฒ˜์˜ API๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒŒ ๋„ˆ๋ฌด๋‚˜ ๋‹น์—ฐํ•œ ์ผ์ด์ž–์•„? ๊ทธ๋ž˜์„œ ๋“ฑ์žฅํ•œ ๊ฒŒ CORS๋ผ๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด์•ผ. ์„œ๋ฒ„๊ฐ€ "์ด ์ถœ์ฒ˜์—์„œ ์˜ค๋Š” ์š”์ฒญ์€ ๊ดœ์ฐฎ์•„!"๋ผ๊ณ  ๋ช…์‹œ์ ์œผ๋กœ ํ—ˆ๋ฝํ•ด์ฃผ๋Š” ๋ฐฉ์‹์ด์ง€.

๐Ÿ’ก CORS์˜ ํ•ต์‹ฌ ๊ฐœ๋…

์„œ๋ฒ„๊ฐ€ ์‘๋‹ต ํ—ค๋”์— ํŠน์ • ์ •๋ณด๋ฅผ ํฌํ•จ์‹œ์ผœ์„œ ๋ธŒ๋ผ์šฐ์ €์—๊ฒŒ "์ด ์š”์ฒญ์€ ํ—ˆ์šฉํ• ๊ฒŒ"๋ผ๊ณ  ์•Œ๋ ค์ฃผ๋Š” ๊ฑฐ์•ผ. ๋Œ€ํ‘œ์ ์ธ ํ—ค๋”๊ฐ€ ๋ฐ”๋กœ:

Access-Control-Allow-Origin: ์–ด๋–ค ์ถœ์ฒ˜์˜ ์š”์ฒญ์„ ํ—ˆ์šฉํ• ์ง€
Access-Control-Allow-Methods: ์–ด๋–ค HTTP ๋ฉ”์„œ๋“œ๋ฅผ ํ—ˆ์šฉํ• ์ง€
Access-Control-Allow-Headers: ์–ด๋–ค ํ—ค๋”๋ฅผ ํ—ˆ์šฉํ• ์ง€

๐Ÿ˜ซ CORS ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ์‹ค์ œ ์ƒํ™ฉ๋“ค

์ด๋ก ์€ ๊ทธ๋ ‡๋‹ค ์น˜๊ณ , ์‹ค์ œ๋กœ ์–ด๋–ค ์ƒํ™ฉ์—์„œ CORS ์—๋Ÿฌ๊ฐ€ ํ„ฐ์ง€๋Š”์ง€ ๋ณผ๊นŒ? ๊ฐœ๋ฐœํ•˜๋‹ค ๋ณด๋ฉด ์ •๋ง ์ž์ฃผ ๋งˆ์ฃผ์น˜๋Š” ์ผ€์ด์Šค๋“ค์ด์•ผ. ๐Ÿ˜…

1๋กœ์ปฌ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ ์™ธ๋ถ€ API ํ˜ธ์ถœ

๊ฐ€์žฅ ํ”ํ•œ ์ผ€์ด์Šค์•ผ! ๋„ˆ์˜ React ์•ฑ์ด localhost:3000์—์„œ ๋Œ์•„๊ฐ€๋Š”๋ฐ, ์™ธ๋ถ€ API (์˜ˆ: https://api.weather.com)๋ฅผ ํ˜ธ์ถœํ•˜๋ ค๊ณ  ํ•˜๋ฉด?

// ์ด๋ ‡๊ฒŒ ์ฝ”๋“œ๋ฅผ ์งฐ๋Š”๋ฐ...
fetch('https://api.weather.com/data')
  .then(response => response.json())
  .then(data => console.log(data));

// ์ฝ˜์†”์—๋Š” ์ด๋Ÿฐ ์—๋Ÿฌ๊ฐ€!
// โŒ Access to fetch at 'https://api.weather.com/data' 
// from origin 'http://localhost:3000' has been blocked by CORS policy

2ํ”„๋ก ํŠธ์—”๋“œ์™€ ๋ฐฑ์—”๋“œ ํฌํŠธ๊ฐ€ ๋‹ค๋ฅผ ๋•Œ

ํ”„๋ก ํŠธ๋Š” 3000๋ฒˆ ํฌํŠธ, ๋ฐฑ์—”๋“œ๋Š” 8080๋ฒˆ ํฌํŠธ์—์„œ ๋Œ๋ฆฌ๋Š” ๊ฒฝ์šฐ. ๊ฐ™์€ localhost์—ฌ๋„ ํฌํŠธ๊ฐ€ ๋‹ค๋ฅด๋ฉด ๋‹ค๋ฅธ ์ถœ์ฒ˜๋กœ ์ธ์‹๋ผ!

// ํ”„๋ก ํŠธ์—”๋“œ (localhost:3000)
fetch('http://localhost:8080/api/users')
  .then(response => response.json());

// ์ด๊ฒƒ๋„ CORS ์—๋Ÿฌ! ํฌํŠธ๊ฐ€ ๋‹ค๋ฅด๊ฑฐ๋“  ๐Ÿ˜ญ

3์„œ๋“œํŒŒํ‹ฐ API๊ฐ€ CORS๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์„ ๋•Œ

์ผ๋ถ€ ์˜ค๋ž˜๋œ API๋‚˜ CORS ์„ค์ •์ด ์•ˆ ๋œ API๋Š” ์•„์˜ˆ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์ง์ ‘ ํ˜ธ์ถœ์ด ๋ถˆ๊ฐ€๋Šฅํ•ด. ํŠนํžˆ ๊ณต๊ณต ๋ฐ์ดํ„ฐ API ์ค‘์— ์ด๋Ÿฐ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•„.

โš ๏ธ ์ฃผ์˜: CORS๋Š” ๋ธŒ๋ผ์šฐ์ €์˜ ์ •์ฑ…์ด์•ผ!

์ค‘์š”ํ•œ ํฌ์ธํŠธ! CORS ์—๋Ÿฌ๋Š” ๋ธŒ๋ผ์šฐ์ €์—์„œ๋งŒ ๋ฐœ์ƒํ•ด. Postman์ด๋‚˜ ์„œ๋ฒ„ ๊ฐ„ ํ†ต์‹ ์—์„œ๋Š” ๋ฌธ์ œ์—†์ด ์ž‘๋™ํ•˜๋Š”๋ฐ ๋ธŒ๋ผ์šฐ์ €์—์„œ๋งŒ ๋ง‰ํžˆ๋Š” ์ด์œ ๊ฐ€ ๋ฐ”๋กœ ์ด๊ฑฐ์•ผ. ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์‚ฌ์šฉ์ž ๋ณด์•ˆ์„ ์œ„ํ•ด ์ถ”๊ฐ€๋กœ ๊ฒ€์‚ฌํ•˜๋Š” ๊ฑฐ์ง€! ๐Ÿ”

๐Ÿ› ๏ธ CORS ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•๋“ค ์ด์ •๋ฆฌ

CORS ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์—ฌ๋Ÿฌ ๊ฐ€์ง€๊ฐ€ ์žˆ์–ด. ์ƒํ™ฉ์— ๋”ฐ๋ผ ์ ์ ˆํ•œ ๋ฐฉ๋ฒ•์„ ์„ ํƒํ•˜๋ฉด ๋ผ! ๊ฐ๊ฐ์˜ ์žฅ๋‹จ์ ์„ ์‚ดํŽด๋ณด์ž. ๐ŸŽฏ

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ• ๋‚œ์ด๋„ ์žฅ์  ๋‹จ์ 
์„œ๋ฒ„์—์„œ CORS ํ—ค๋” ์„ค์ • โญโญ ๊ทผ๋ณธ์  ํ•ด๊ฒฐ, ์•ˆ์ •์  ์„œ๋ฒ„ ์ ‘๊ทผ ๊ถŒํ•œ ํ•„์š”
ํ”„๋ก์‹œ ์„œ๋ฒ„ ์‚ฌ์šฉ โญโญโญ ์„œ๋ฒ„ ์ˆ˜์ • ๋ถˆํ•„์š”, ์œ ์—ฐํ•จ ์ถ”๊ฐ€ ์„œ๋ฒ„ ํ•„์š”
๋ธŒ๋ผ์šฐ์ € ํ™•์žฅ ํ”„๋กœ๊ทธ๋žจ โญ ์ฆ‰์‹œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ ๊ฐœ๋ฐœ์šฉ์œผ๋กœ๋งŒ, ๋ณด์•ˆ ์œ„ํ—˜
JSONP ๋ฐฉ์‹ โญโญ ์˜ค๋ž˜๋œ ๋ธŒ๋ผ์šฐ์ € ์ง€์› GET๋งŒ ๊ฐ€๋Šฅ, ๊ตฌ์‹ ๋ฐฉ๋ฒ•

์ด ์ค‘์—์„œ ์˜ค๋Š˜ ์šฐ๋ฆฌ๊ฐ€ ์ง‘์ค‘ํ•  ๊ฑด ๋ฐ”๋กœ ํ”„๋ก์‹œ ์„œ๋ฒ„ ๋ฐฉ์‹์ด์•ผ! ์™œ๋ƒํ•˜๋ฉด:

โœ… ์„œ๋ฒ„ ์ˆ˜์ • ๋ถˆํ•„์š”

์™ธ๋ถ€ API ์„œ๋ฒ„๋ฅผ ๊ฑด๋“œ๋ฆด ํ•„์š”๊ฐ€ ์—†์–ด. ํŠนํžˆ ์„œ๋“œํŒŒํ‹ฐ API ์‚ฌ์šฉ ์‹œ ์™„๋ฒฝ!

โœ… ์‹ค์ œ ๋ฐฐํฌ ํ™˜๊ฒฝ์— ์ ํ•ฉ

๊ฐœ๋ฐœ์šฉ ์ž„์‹œ๋ฐฉํŽธ์ด ์•„๋‹ˆ๋ผ ํ”„๋กœ๋•์…˜์—์„œ๋„ ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ •์„ ๋ฐฉ๋ฒ•์ด์•ผ.

โœ… ์ถ”๊ฐ€ ๊ธฐ๋Šฅ ๊ตฌํ˜„ ๊ฐ€๋Šฅ

์บ์‹ฑ, ๋กœ๊น…, ์ธ์ฆ ์ฒ˜๋ฆฌ ๋“ฑ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ์„ ํ”„๋ก์‹œ์— ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ์–ด.

ํ”„๋ก์‹œ ์„œ๋ฒ„ ๋™์ž‘ ์›๋ฆฌ ํด๋ผ์ด์–ธํŠธ (๋ธŒ๋ผ์šฐ์ €) localhost:3000 ํ”„๋ก์‹œ ์„œ๋ฒ„ (Node.js) localhost:5000 ๐Ÿ”„ ์™ธ๋ถ€ API (์„œ๋ฒ„) api.example.com 1. ์š”์ฒญ (๊ฐ™์€ ์ถœ์ฒ˜ OK!) 2. ์ „๋‹ฌ (์„œ๋ฒ„ ๊ฐ„ ํ†ต์‹ ) 3. ์‘๋‹ต 4. ๋ฐ˜ํ™˜ ํ•ต์‹ฌ ํฌ์ธํŠธ ๋ธŒ๋ผ์šฐ์ €๋Š” ๊ฐ™์€ ์ถœ์ฒ˜(localhost:5000)๋กœ ์š”์ฒญํ•˜๋ฏ€๋กœ CORS ๋ฌธ์ œ ์—†์Œ ํ”„๋ก์‹œ ์„œ๋ฒ„๊ฐ€ ๋Œ€์‹  ์™ธ๋ถ€ API๋ฅผ ํ˜ธ์ถœ (์„œ๋ฒ„ ๊ฐ„ ํ†ต์‹ ์€ CORS ๋ฌด๊ด€) ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ ๊ทธ๋Œ€๋กœ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์ „๋‹ฌ

๐Ÿš€ ํ”„๋ก์‹œ ์„œ๋ฒ„๋ž€ ๋ฌด์—‡์ธ๊ฐ€?

ํ”„๋ก์‹œ(Proxy)๋Š” '๋Œ€๋ฆฌ'๋ผ๋Š” ๋œป์ด์•ผ. ํ”„๋ก์‹œ ์„œ๋ฒ„๋Š” ๋ง ๊ทธ๋Œ€๋กœ ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ์‚ฌ์ด์—์„œ ์ค‘๊ฐœ์ž ์—ญํ• ์„ ํ•˜๋Š” ์„œ๋ฒ„์ง€! ๐Ÿค

์ƒ๊ฐํ•ด๋ด. ๋„ˆ๊ฐ€ ํ•ด์™ธ ์ง๊ตฌ๋ฅผ ํ•  ๋•Œ ๋ฐฐ์†ก๋Œ€ํ–‰์ง€๋ฅผ ์“ฐ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ, ํ”„๋ก์‹œ ์„œ๋ฒ„๋„ ๋น„์Šทํ•œ ์—ญํ• ์„ ํ•ด. ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ง์ ‘ ์™ธ๋ถ€ API์— ์ ‘๊ทผํ•˜๋Š” ๊ฒŒ ์•„๋‹ˆ๋ผ, ํ”„๋ก์‹œ ์„œ๋ฒ„์—๊ฒŒ "์ด๊ฑฐ ์ข€ ๋Œ€์‹  ๊ฐ€์ ธ๋‹ค์ค˜!"๋ผ๊ณ  ๋ถ€ํƒํ•˜๋Š” ๊ฑฐ์•ผ. ๐Ÿ“ฆ

๐ŸŽฏ ํ”„๋ก์‹œ ์„œ๋ฒ„์˜ ์ž‘๋™ ๋ฐฉ์‹

Step 1: ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํ”„๋ก์‹œ ์„œ๋ฒ„์— ์š”์ฒญ (๊ฐ™์€ ์ถœ์ฒ˜์ด๋ฏ€๋กœ CORS ๋ฌธ์ œ ์—†์Œ)
Step 2: ํ”„๋ก์‹œ ์„œ๋ฒ„๊ฐ€ ์™ธ๋ถ€ API์— ์š”์ฒญ (์„œ๋ฒ„ ๊ฐ„ ํ†ต์‹ ์ด๋ฏ€๋กœ CORS ๋ฌด๊ด€)
Step 3: ์™ธ๋ถ€ API๊ฐ€ ํ”„๋ก์‹œ ์„œ๋ฒ„์— ์‘๋‹ต
Step 4: ํ”„๋ก์‹œ ์„œ๋ฒ„๊ฐ€ ๋ธŒ๋ผ์šฐ์ €์— ์‘๋‹ต ์ „๋‹ฌ

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ € ์ž…์žฅ์—์„œ๋Š” ๊ฐ™์€ ์ถœ์ฒ˜์˜ ์„œ๋ฒ„์™€๋งŒ ํ†ต์‹ ํ•˜๋Š” ๊ฑฐ๋‹ˆ๊นŒ CORS ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์•„! ๐ŸŽ‰

ํ”„๋ก์‹œ ์„œ๋ฒ„์˜ ์žฅ์ ์€ ๋‹จ์ˆœํžˆ CORS ํ•ด๊ฒฐ์—๋งŒ ๊ทธ์น˜์ง€ ์•Š์•„:

๐Ÿ” ๋ณด์•ˆ ๊ฐ•ํ™”

API ํ‚ค๋ฅผ ํด๋ผ์ด์–ธํŠธ์— ๋…ธ์ถœํ•˜์ง€ ์•Š๊ณ  ์„œ๋ฒ„์—์„œ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ์–ด.

โšก ์„ฑ๋Šฅ ์ตœ์ ํ™”

์ž์ฃผ ์š”์ฒญํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์บ์‹ฑํ•ด์„œ ์‘๋‹ต ์†๋„๋ฅผ ๋†’์ผ ์ˆ˜ ์žˆ์ง€.

๐Ÿ“Š ๋กœ๊น… & ๋ชจ๋‹ˆํ„ฐ๋ง

๋ชจ๋“  ์š”์ฒญ์„ ์ค‘์•™์—์„œ ๊ด€๋ฆฌํ•˜๊ณ  ๊ธฐ๋กํ•  ์ˆ˜ ์žˆ์–ด.

๐Ÿ”„ ์š”์ฒญ/์‘๋‹ต ๋ณ€ํ™˜

๋ฐ์ดํ„ฐ ํ˜•์‹์„ ๋ณ€ํ™˜ํ•˜๊ฑฐ๋‚˜ ์ถ”๊ฐ€ ์ฒ˜๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ์–ด.

๐Ÿ’ป Node.js๋กœ ๊ฐ„๋‹จํ•œ ํ”„๋ก์‹œ ์„œ๋ฒ„ ๋งŒ๋“ค๊ธฐ

์ž, ์ด์ œ ๋ณธ๊ฒฉ์ ์œผ๋กœ ํ”„๋ก์‹œ ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค์–ด๋ณผ ์‹œ๊ฐ„์ด์•ผ! ๊ฐ€์žฅ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ์ ์  ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ๊นŒ์ง€ ์•Œ์•„๋ณด์ž. ๐Ÿ˜Ž

๋ฐฉ๋ฒ• 1: http-proxy-middleware ์‚ฌ์šฉํ•˜๊ธฐ

๊ฐ€์žฅ ์‰ฝ๊ณ  ๋งŽ์ด ์“ฐ์ด๋Š” ๋ฐฉ๋ฒ•์ด์•ผ. Express์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๋ฉด ์ •๋ง ๊ฐ„๋‹จํ•ด! ๋จผ์ € ํ•„์š”ํ•œ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•˜์ž:

npm install express http-proxy-middleware cors

๊ทธ๋ฆฌ๊ณ  ์ด๋ ‡๊ฒŒ ํ”„๋ก์‹œ ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค์–ด:

// proxy-server.js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const cors = require('cors');

const app = express();
const PORT = 5000;

// CORS ์„ค์ • (๋ชจ๋“  ์ถœ์ฒ˜ ํ—ˆ์šฉ)
app.use(cors());

// ํ”„๋ก์‹œ ๋ฏธ๋“ค์›จ์–ด ์„ค์ •
app.use('/api', createProxyMiddleware({
  target: 'https://api.example.com', // ์‹ค์ œ API ์„œ๋ฒ„ ์ฃผ์†Œ
  changeOrigin: true, // ํ˜ธ์ŠคํŠธ ํ—ค๋”๋ฅผ ํƒ€๊ฒŸ URL๋กœ ๋ณ€๊ฒฝ
  pathRewrite: {
    '^/api': '', // /api ๊ฒฝ๋กœ๋ฅผ ์ œ๊ฑฐํ•˜๊ณ  ์ „๋‹ฌ
  },
  onProxyReq: (proxyReq, req, res) => {
    // ์š”์ฒญ ์ „ ๋กœ๊น…
    console.log(`[ํ”„๋ก์‹œ ์š”์ฒญ] ${req.method} ${req.path}`);
  },
  onProxyRes: (proxyRes, req, res) => {
    // ์‘๋‹ต ํ›„ ๋กœ๊น…
    console.log(`[ํ”„๋ก์‹œ ์‘๋‹ต] ${proxyRes.statusCode}`);
  },
  onError: (err, req, res) => {
    // ์—๋Ÿฌ ์ฒ˜๋ฆฌ
    console.error('[ํ”„๋ก์‹œ ์—๋Ÿฌ]', err);
    res.status(500).json({ error: 'ํ”„๋ก์‹œ ์„œ๋ฒ„ ์—๋Ÿฌ ๋ฐœ์ƒ' });
  }
}));

// ์„œ๋ฒ„ ์‹œ์ž‘
app.listen(PORT, () => {
  console.log(`๐Ÿš€ ํ”„๋ก์‹œ ์„œ๋ฒ„๊ฐ€ ํฌํŠธ ${PORT}์—์„œ ์‹คํ–‰ ์ค‘์ž…๋‹ˆ๋‹ค!`);
});

์ด์ œ ํด๋ผ์ด์–ธํŠธ์—์„œ๋Š” ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉํ•˜๋ฉด ๋ผ:

// ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ (React, Vue ๋“ฑ)
// ๊ธฐ์กด: fetch('https://api.example.com/users') โŒ CORS ์—๋Ÿฌ!
// ๋ณ€๊ฒฝ: fetch('http://localhost:5000/api/users') โœ… ์ •์ƒ ์ž‘๋™!

fetch('http://localhost:5000/api/users')
  .then(response => response.json())
  .then(data => {
    console.log('๋ฐ์ดํ„ฐ ๋ฐ›๊ธฐ ์„ฑ๊ณต!', data);
  })
  .catch(error => {
    console.error('์—๋Ÿฌ ๋ฐœ์ƒ:', error);
  });

๐Ÿ” ์ฝ”๋“œ ์„ค๋ช…

target: ์‹ค์ œ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ผ API ์„œ๋ฒ„ ์ฃผ์†Œ
changeOrigin: true๋กœ ์„ค์ •ํ•˜๋ฉด ํ˜ธ์ŠคํŠธ ํ—ค๋”๋ฅผ ํƒ€๊ฒŸ URL๋กœ ๋ณ€๊ฒฝํ•ด์ค˜. ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ํ•„์š”ํ•ด!
pathRewrite: ๊ฒฝ๋กœ๋ฅผ ๋ณ€ํ™˜ํ•ด์ค˜. ์˜ˆ๋ฅผ ๋“ค์–ด /api๋ฅผ ์ œ๊ฑฐํ•˜๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉ
onProxyReq/onProxyRes: ์š”์ฒญ/์‘๋‹ต ์‹œ ์ถ”๊ฐ€ ์ž‘์—…์„ ํ•  ์ˆ˜ ์žˆ๋Š” ํ›…(hook) ํ•จ์ˆ˜๋“ค

๋ฐฉ๋ฒ• 2: ์ง์ ‘ fetch๋กœ ํ”„๋ก์‹œ ๊ตฌํ˜„ํ•˜๊ธฐ

http-proxy-middleware ์—†์ด ์ง์ ‘ ๊ตฌํ˜„ํ•  ์ˆ˜๋„ ์žˆ์–ด. ๋” ์„ธ๋ฐ€ํ•œ ์ œ์–ด๊ฐ€ ํ•„์š”ํ•  ๋•Œ ์œ ์šฉํ•˜์ง€! ๐ŸŽจ

// simple-proxy.js
const express = require('express');
const cors = require('cors');
const fetch = require('node-fetch'); // Node.js 18 ๋ฏธ๋งŒ์—์„œ๋Š” ์„ค์น˜ ํ•„์š”

const app = express();
const PORT = 5000;

app.use(cors());
app.use(express.json()); // JSON ํŒŒ์‹ฑ

// GET ์š”์ฒญ ํ”„๋ก์‹œ
app.get('/api/*', async (req, res) => {
  try {
    // ์›๋ณธ ๊ฒฝ๋กœ์—์„œ /api ์ œ๊ฑฐ
    const targetPath = req.path.replace('/api', '');
    const targetUrl = `https://api.example.com${targetPath}`;
    
    // ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ ํฌํ•จ
    const queryString = new URLSearchParams(req.query).toString();
    const fullUrl = queryString ? `${targetUrl}?${queryString}` : targetUrl;
    
    console.log(`๐Ÿ“ก ์š”์ฒญ ์ „๋‹ฌ: ${fullUrl}`);
    
    // ์™ธ๋ถ€ API ํ˜ธ์ถœ
    const response = await fetch(fullUrl, {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
        // ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์ธ์ฆ ํ—ค๋” ์ถ”๊ฐ€
        // 'Authorization': 'Bearer YOUR_API_KEY'
      }
    });
    
    const data = await response.json();
    
    // ํด๋ผ์ด์–ธํŠธ์— ์‘๋‹ต
    res.status(response.status).json(data);
    
  } catch (error) {
    console.error('โŒ ํ”„๋ก์‹œ ์—๋Ÿฌ:', error);
    res.status(500).json({ 
      error: '์„œ๋ฒ„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค',
      message: error.message 
    });
  }
});

// POST ์š”์ฒญ ํ”„๋ก์‹œ
app.post('/api/*', async (req, res) => {
  try {
    const targetPath = req.path.replace('/api', '');
    const targetUrl = `https://api.example.com${targetPath}`;
    
    console.log(`๐Ÿ“ค POST ์š”์ฒญ ์ „๋‹ฌ: ${targetUrl}`);
    console.log('๐Ÿ“ฆ ์š”์ฒญ ๋ณธ๋ฌธ:', req.body);
    
    const response = await fetch(targetUrl, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(req.body)
    });
    
    const data = await response.json();
    res.status(response.status).json(data);
    
  } catch (error) {
    console.error('โŒ ํ”„๋ก์‹œ ์—๋Ÿฌ:', error);
    res.status(500).json({ 
      error: '์„œ๋ฒ„ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค',
      message: error.message 
    });
  }
});

app.listen(PORT, () => {
  console.log(`๐Ÿš€ ์ปค์Šคํ…€ ํ”„๋ก์‹œ ์„œ๋ฒ„ ์‹คํ–‰ ์ค‘: http://localhost:${PORT}`);
});

์ด ๋ฐฉ๋ฒ•์˜ ์žฅ์ ์€ ์š”์ฒญ/์‘๋‹ต์„ ์™„์ „ํžˆ ์ œ์–ดํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฑฐ์•ผ. ์˜ˆ๋ฅผ ๋“ค์–ด:

โœจ ์‘๋‹ต ๋ฐ์ดํ„ฐ ๊ฐ€๊ณตํ•˜๊ธฐ

const data = await response.json();

// ๋ฐ์ดํ„ฐ ๊ฐ€๊ณต ์˜ˆ์‹œ
const processedData = {
  ...data,
  timestamp: new Date().toISOString(),
  source: 'proxy-server'
};

res.json(processedData);

๐Ÿ” API ํ‚ค ์ˆจ๊ธฐ๊ธฐ

// ํ™˜๊ฒฝ ๋ณ€์ˆ˜์—์„œ API ํ‚ค ๊ฐ€์ ธ์˜ค๊ธฐ
const API_KEY = process.env.API_KEY;

const response = await fetch(targetUrl, {
  headers: {
    'Authorization': `Bearer ${API_KEY}`,
    'Content-Type': 'application/json'
  }
});

// ํด๋ผ์ด์–ธํŠธ๋Š” API ํ‚ค๋ฅผ ๋ชจ๋ฅด๋Š” ์ƒํƒœ๋กœ ์‚ฌ์šฉ ๊ฐ€๋Šฅ!

๋ฐฉ๋ฒ• 3: Create React App์˜ ํ”„๋ก์‹œ ์„ค์ •

React ๊ฐœ๋ฐœ์ž๋ผ๋ฉด ์ด ๋ฐฉ๋ฒ•์ด ๊ฐ€์žฅ ๊ฐ„๋‹จํ•  ์ˆ˜๋„ ์žˆ์–ด! Create React App์—๋Š” ํ”„๋ก์‹œ ๊ธฐ๋Šฅ์ด ๋‚ด์žฅ๋˜์–ด ์žˆ๊ฑฐ๋“ . ๐ŸŽ‰

package.json์— ํ•œ ์ค„๋งŒ ์ถ”๊ฐ€:

{
  "name": "my-app",
  "version": "0.1.0",
  "proxy": "http://localhost:8080",  // ๐Ÿ‘ˆ ์ด๊ฒƒ๋งŒ ์ถ”๊ฐ€!
  "dependencies": {
    "react": "^18.0.0",
    ...
  }
}

์ด์ œ ์ƒ๋Œ€ ๊ฒฝ๋กœ๋กœ API๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ์ž๋™์œผ๋กœ ํ”„๋ก์‹œ๋ผ:

// ์ด๋ ‡๊ฒŒ๋งŒ ํ•˜๋ฉด ์ž๋™์œผ๋กœ http://localhost:8080/api/users๋กœ ์š”์ฒญ๋จ!
fetch('/api/users')
  .then(response => response.json())
  .then(data => console.log(data));

๋” ๋ณต์žกํ•œ ์„ค์ •์ด ํ•„์š”ํ•˜๋ฉด src/setupProxy.js ํŒŒ์ผ์„ ๋งŒ๋“ค์–ด:

// src/setupProxy.js
const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
  // ์—ฌ๋Ÿฌ API ์—”๋“œํฌ์ธํŠธ ์„ค์ • ๊ฐ€๋Šฅ
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'http://localhost:8080',
      changeOrigin: true,
    })
  );
  
  app.use(
    '/auth',
    createProxyMiddleware({
      target: 'http://localhost:9000',
      changeOrigin: true,
    })
  );
  
  // ์™ธ๋ถ€ API๋„ ๊ฐ€๋Šฅ
  app.use(
    '/external',
    createProxyMiddleware({
      target: 'https://api.external.com',
      changeOrigin: true,
      pathRewrite: {
        '^/external': '', // /external ์ œ๊ฑฐ
      },
    })
  );
};

๐Ÿ’ก CRA ํ”„๋ก์‹œ ์‚ฌ์šฉ ์‹œ ์ฃผ์˜์‚ฌํ•ญ

โœ… ๊ฐœ๋ฐœ ์„œ๋ฒ„(npm start)์—์„œ๋งŒ ์ž‘๋™ํ•ด
โœ… ํ”„๋กœ๋•์…˜ ๋นŒ๋“œ์—๋Š” ์ ์šฉ๋˜์ง€ ์•Š์•„
โœ… ๋ฐฐํฌ ์‹œ์—๋Š” ๋ณ„๋„์˜ ํ”„๋ก์‹œ ์„œ๋ฒ„๋‚˜ ์„œ๋ฒ„ ์„ค์ • ํ•„์š”
โœ… setupProxy.js๋Š” ์žฌ์‹œ์ž‘ ์—†์ด ์ž๋™ ์ ์šฉ๋ผ

๐ŸŽจ ์‹ค์ „ ํ”„๋ก์‹œ ์„œ๋ฒ„ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ

๊ธฐ๋ณธ์ ์ธ ํ”„๋ก์‹œ๋Š” ๋งŒ๋“ค์—ˆ์œผ๋‹ˆ, ์ด์ œ ์‹ค๋ฌด์—์„œ ์œ ์šฉํ•œ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ๋“ค์„ ์ถ”๊ฐ€ํ•ด๋ณด์ž! ์žฌ๋Šฅ๋„ท์—์„œ ์›น ๊ฐœ๋ฐœ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•  ๋•Œ๋„ ์ด๋Ÿฐ ๊ธฐ๋Šฅ๋“ค์ด ์ •๋ง ์œ ์šฉํ•  ๊ฑฐ์•ผ. ๐Ÿ’ช

1๏ธโƒฃ ์บ์‹ฑ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ํ•˜๊ธฐ

๊ฐ™์€ ์š”์ฒญ์„ ๋ฐ˜๋ณตํ•˜๋ฉด ๋งค๋ฒˆ ์™ธ๋ถ€ API๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฑด ๋น„ํšจ์œจ์ ์ด์•ผ. ์บ์‹ฑ์„ ์ถ”๊ฐ€ํ•ด์„œ ์„ฑ๋Šฅ์„ ๋†’์—ฌ๋ณด์ž! โšก

// cache-proxy.js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const cors = require('cors');
const NodeCache = require('node-cache'); // npm install node-cache

const app = express();
const PORT = 5000;

// ์บ์‹œ ์„ค์ • (TTL: 5๋ถ„)
const cache = new NodeCache({ stdTTL: 300 });

app.use(cors());

// ์บ์‹œ ๋ฏธ๋“ค์›จ์–ด
const cacheMiddleware = (req, res, next) => {
  // GET ์š”์ฒญ๋งŒ ์บ์‹ฑ
  if (req.method !== 'GET') {
    return next();
  }
  
  const key = req.originalUrl;
  const cachedResponse = cache.get(key);
  
  if (cachedResponse) {
    console.log(`โœ… ์บ์‹œ ํžˆํŠธ: ${key}`);
    return res.json(cachedResponse);
  }
  
  console.log(`โŒ ์บ์‹œ ๋ฏธ์Šค: ${key}`);
  
  // ์›๋ž˜ res.json์„ ์ €์žฅ
  const originalJson = res.json.bind(res);
  
  // res.json์„ ์˜ค๋ฒ„๋ผ์ด๋“œํ•ด์„œ ์บ์‹œ์— ์ €์žฅ
  res.json = (data) => {
    cache.set(key, data);
    console.log(`๐Ÿ’พ ์บ์‹œ ์ €์žฅ: ${key}`);
    return originalJson(data);
  };
  
  next();
};

// ์บ์‹œ ๋ฏธ๋“ค์›จ์–ด ์ ์šฉ
app.use('/api', cacheMiddleware);

// ํ”„๋ก์‹œ ์„ค์ •
app.use('/api', createProxyMiddleware({
  target: 'https://api.example.com',
  changeOrigin: true,
  pathRewrite: { '^/api': '' }
}));

// ์บ์‹œ ์ดˆ๊ธฐํ™” ์—”๋“œํฌ์ธํŠธ
app.delete('/cache', (req, res) => {
  cache.flushAll();
  res.json({ message: '์บ์‹œ๊ฐ€ ๋ชจ๋‘ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค' });
});

// ์บ์‹œ ์ƒํƒœ ํ™•์ธ
app.get('/cache/stats', (req, res) => {
  const stats = cache.getStats();
  res.json({
    keys: cache.keys().length,
    hits: stats.hits,
    misses: stats.misses,
    hitRate: stats.hits / (stats.hits + stats.misses)
  });
});

app.listen(PORT, () => {
  console.log(`๐Ÿš€ ์บ์‹ฑ ํ”„๋ก์‹œ ์„œ๋ฒ„ ์‹คํ–‰ ์ค‘: ${PORT}`);
});

2๏ธโƒฃ Rate Limiting (์š”์ฒญ ์ œํ•œ)

์™ธ๋ถ€ API๊ฐ€ ์š”์ฒญ ํšŸ์ˆ˜ ์ œํ•œ์ด ์žˆ๋‹ค๋ฉด? ํ”„๋ก์‹œ ์„œ๋ฒ„์—์„œ ๋ฏธ๋ฆฌ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์–ด! ๐Ÿšฆ

// rate-limit-proxy.js
const express = require('express');
const rateLimit = require('express-rate-limit'); // npm install express-rate-limit
const { createProxyMiddleware } = require('http-proxy-middleware');

const app = express();

// Rate Limiter ์„ค์ •
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15๋ถ„
  max: 100, // ์ตœ๋Œ€ 100๊ฐœ ์š”์ฒญ
  message: {
    error: '๋„ˆ๋ฌด ๋งŽ์€ ์š”์ฒญ์„ ๋ณด๋ƒˆ์Šต๋‹ˆ๋‹ค. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.',
    retryAfter: '15๋ถ„'
  },
  standardHeaders: true, // RateLimit-* ํ—ค๋” ์ถ”๊ฐ€
  legacyHeaders: false,
  handler: (req, res) => {
    console.log(`โš ๏ธ Rate limit ์ดˆ๊ณผ: ${req.ip}`);
    res.status(429).json({
      error: '์š”์ฒญ ํ•œ๋„๋ฅผ ์ดˆ๊ณผํ–ˆ์Šต๋‹ˆ๋‹ค',
      message: '15๋ถ„ ํ›„์— ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”'
    });
  }
});

// ํŠน์ • ๊ฒฝ๋กœ์—๋งŒ ์ ์šฉ
app.use('/api', limiter);

// ํ”„๋ก์‹œ ์„ค์ •
app.use('/api', createProxyMiddleware({
  target: 'https://api.example.com',
  changeOrigin: true
}));

app.listen(5000, () => {
  console.log('๐Ÿš€ Rate Limiting ํ”„๋ก์‹œ ์„œ๋ฒ„ ์‹คํ–‰ ์ค‘');
});

3๏ธโƒฃ ์ธ์ฆ & ๋ณด์•ˆ ๊ฐ•ํ™”

API ํ‚ค๋ฅผ ํด๋ผ์ด์–ธํŠธ์— ๋…ธ์ถœํ•˜์ง€ ์•Š๊ณ  ์„œ๋ฒ„์—์„œ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•! ๐Ÿ”

// secure-proxy.js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
require('dotenv').config(); // npm install dotenv

const app = express();

// ํ™˜๊ฒฝ ๋ณ€์ˆ˜์—์„œ API ํ‚ค ๋กœ๋“œ
const API_KEY = process.env.EXTERNAL_API_KEY;
const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS?.split(',') || [];

// CORS ์„ค์ • (ํŠน์ • ์ถœ์ฒ˜๋งŒ ํ—ˆ์šฉ)
app.use((req, res, next) => {
  const origin = req.headers.origin;
  
  if (ALLOWED_ORIGINS.includes(origin)) {
    res.setHeader('Access-Control-Allow-Origin', origin);
    res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
    res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization');
  }
  
  if (req.method === 'OPTIONS') {
    return res.sendStatus(200);
  }
  
  next();
});

// ๊ฐ„๋‹จํ•œ ์ธ์ฆ ๋ฏธ๋“ค์›จ์–ด
const authenticate = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  
  // ์‹ค์ œ๋กœ๋Š” JWT ๊ฒ€์ฆ ๋“ฑ์„ ์ˆ˜ํ–‰
  if (!token || token !== process.env.CLIENT_TOKEN) {
    return res.status(401).json({ error: '์ธ์ฆ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค' });
  }
  
  next();
};

// ์ธ์ฆ ์ ์šฉ
app.use('/api', authenticate);

// ํ”„๋ก์‹œ ์„ค์ • (API ํ‚ค ์ž๋™ ์ถ”๊ฐ€)
app.use('/api', createProxyMiddleware({
  target: 'https://api.example.com',
  changeOrigin: true,
  onProxyReq: (proxyReq, req, res) => {
    // ๋ชจ๋“  ์š”์ฒญ์— API ํ‚ค ์ž๋™ ์ถ”๊ฐ€
    proxyReq.setHeader('X-API-Key', API_KEY);
    console.log('๐Ÿ”‘ API ํ‚ค ์ž๋™ ์ถ”๊ฐ€๋จ');
  }
}));

app.listen(5000);

.env ํŒŒ์ผ ์˜ˆ์‹œ:

# .env
EXTERNAL_API_KEY=your_secret_api_key_here
CLIENT_TOKEN=your_client_token_here
ALLOWED_ORIGINS=http://localhost:3000,https://yourdomain.com

4๏ธโƒฃ ๋กœ๊น… & ๋ชจ๋‹ˆํ„ฐ๋ง

๋ชจ๋“  ์š”์ฒญ์„ ๊ธฐ๋กํ•˜๊ณ  ๋ถ„์„ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“ค์–ด๋ณด์ž! ๐Ÿ“Š

// logging-proxy.js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const morgan = require('morgan'); // npm install morgan
const fs = require('fs');
const path = require('path');

const app = express();

// ๋กœ๊ทธ ๋””๋ ‰ํ† ๋ฆฌ ์ƒ์„ฑ
const logDirectory = path.join(__dirname, 'logs');
if (!fs.existsSync(logDirectory)) {
  fs.mkdirSync(logDirectory);
}

// ๋กœ๊ทธ ํŒŒ์ผ ์ŠคํŠธ๋ฆผ
const accessLogStream = fs.createWriteStream(
  path.join(logDirectory, 'access.log'),
  { flags: 'a' }
);

// Morgan ๋กœ๊น… ์„ค์ •
app.use(morgan('combined', { stream: accessLogStream }));
app.use(morgan('dev')); // ์ฝ˜์†”์—๋„ ์ถœ๋ ฅ

// ์ปค์Šคํ…€ ๋กœ๊น… ๋ฏธ๋“ค์›จ์–ด
app.use((req, res, next) => {
  const start = Date.now();
  
  res.on('finish', () => {
    const duration = Date.now() - start;
    const log = {
      timestamp: new Date().toISOString(),
      method: req.method,
      url: req.originalUrl,
      status: res.statusCode,
      duration: `${duration}ms`,
      ip: req.ip,
      userAgent: req.get('user-agent')
    };
    
    console.log('๐Ÿ“ ์š”์ฒญ ๋กœ๊ทธ:', JSON.stringify(log));
    
    // ํŒŒ์ผ์— JSON ํ˜•์‹์œผ๋กœ ์ €์žฅ
    fs.appendFileSync(
      path.join(logDirectory, 'requests.json'),
      JSON.stringify(log) + '\n'
    );
  });
  
  next();
});

// ํ”„๋ก์‹œ ์„ค์ •
app.use('/api', createProxyMiddleware({
  target: 'https://api.example.com',
  changeOrigin: true,
  onProxyReq: (proxyReq, req, res) => {
    console.log(`โžก๏ธ  ํ”„๋ก์‹œ ์š”์ฒญ: ${req.method} ${req.path}`);
  },
  onProxyRes: (proxyRes, req, res) => {
    console.log(`โฌ…๏ธ  ํ”„๋ก์‹œ ์‘๋‹ต: ${proxyRes.statusCode}`);
  },
  onError: (err, req, res) => {
    console.error('โŒ ํ”„๋ก์‹œ ์—๋Ÿฌ:', err.message);
    
    // ์—๋Ÿฌ ๋กœ๊ทธ ์ €์žฅ
    const errorLog = {
      timestamp: new Date().toISOString(),
      error: err.message,
      url: req.originalUrl,
      method: req.method
    };
    
    fs.appendFileSync(
      path.join(logDirectory, 'errors.json'),
      JSON.stringify(errorLog) + '\n'
    );
  }
}));

// ๋กœ๊ทธ ์กฐํšŒ API
app.get('/logs/recent', (req, res) => {
  const logs = fs.readFileSync(
    path.join(logDirectory, 'requests.json'),
    'utf-8'
  )
    .split('\n')
    .filter(line => line)
    .map(line => JSON.parse(line))
    .slice(-50); // ์ตœ๊ทผ 50๊ฐœ
  
  res.json(logs);
});

app.listen(5000);
ํ”„๋ก์‹œ ์„œ๋ฒ„ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ ๊ตฌ์กฐ ํ”„๋ก์‹œ ์„œ๋ฒ„ ๋ ˆ์ด์–ด ๐Ÿ” ์ธ์ฆ ๋ ˆ์ด์–ด ๐Ÿšฆ Rate Limiting ๐Ÿ’พ ์บ์‹ฑ ๋ ˆ์ด์–ด ๐Ÿ“Š ๋กœ๊น… & ๋ชจ๋‹ˆํ„ฐ๋ง ๐Ÿ‘ค ํด๋ผ์ด์–ธํŠธ ๐ŸŒ ์™ธ๋ถ€ API ์š”์ฒญ ์‘๋‹ต

๐ŸŒ ์‹ค์ „ ๋ฐฐํฌ: ํ”„๋ก์‹œ ์„œ๋ฒ„ ์šด์˜ํ•˜๊ธฐ

๊ฐœ๋ฐœ ํ™˜๊ฒฝ์—์„œ๋Š” ์ž˜ ์ž‘๋™ํ•˜๋Š”๋ฐ, ์‹ค์ œ ๋ฐฐํฌ๋Š” ์–ด๋–ป๊ฒŒ ํ• ๊นŒ? ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ ํ”„๋ก์‹œ ์„œ๋ฒ„๋ฅผ ์•ˆ์ •์ ์œผ๋กœ ์šด์˜ํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์ž! ๐Ÿš€

๋ฐฐํฌ ์˜ต์…˜ ๋น„๊ต

๋ฐฐํฌ ๋ฐฉ์‹ ๋‚œ์ด๋„ ๋น„์šฉ ์žฅ์  ์ถ”์ฒœ ๋Œ€์ƒ
Heroku โญ ๋ฌด๋ฃŒ~์ €๋ ด ๊ฐ„ํŽธํ•œ ๋ฐฐํฌ, Git ์—ฐ๋™ ๊ฐœ์ธ ํ”„๋กœ์ ํŠธ, ํ”„๋กœํ† ํƒ€์ž…
Vercel/Netlify โญ ๋ฌด๋ฃŒ~์ €๋ ด ์„œ๋ฒ„๋ฆฌ์Šค, ์ž๋™ ์Šค์ผ€์ผ๋ง ์ž‘์€ ๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ
AWS EC2 โญโญโญ ์ค‘๊ฐ„ ์™„์ „ํ•œ ์ œ์–ด, ํ™•์žฅ์„ฑ ์ค‘๋Œ€ํ˜• ํ”„๋กœ์ ํŠธ
Docker + ํด๋ผ์šฐ๋“œ โญโญโญโญ ์ค‘๊ฐ„~๋†’์Œ ์ด์‹์„ฑ, ํ™•์žฅ์„ฑ, ๊ด€๋ฆฌ ์šฉ์ด ์—”ํ„ฐํ”„๋ผ์ด์ฆˆ๊ธ‰

Heroku์— ๋ฐฐํฌํ•˜๊ธฐ (๊ฐ€์žฅ ์‰ฌ์šด ๋ฐฉ๋ฒ•)

Heroku๋Š” ์ •๋ง ๊ฐ„๋‹จํ•ด! Git push๋งŒ ํ•˜๋ฉด ์ž๋™์œผ๋กœ ๋ฐฐํฌ๋ผ. ๐Ÿ˜Ž

1package.json ์„ค์ •

{
  "name": "cors-proxy-server",
  "version": "1.0.0",
  "main": "server.js",
  "scripts": {
    "start": "node server.js"
  },
  "engines": {
    "node": "18.x"  // Node.js ๋ฒ„์ „ ๋ช…์‹œ
  },
  "dependencies": {
    "express": "^4.18.0",
    "http-proxy-middleware": "^2.0.0",
    "cors": "^2.8.5"
  }
}

2Procfile ์ƒ์„ฑ

web: node server.js

3ํฌํŠธ ์„ค์ • ์ˆ˜์ •

// server.js
const PORT = process.env.PORT || 5000;  // Heroku๊ฐ€ ํฌํŠธ๋ฅผ ์ž๋™ ํ• ๋‹น

app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});

4Heroku CLI๋กœ ๋ฐฐํฌ

# Heroku CLI ์„ค์น˜ ํ›„
heroku login
heroku create my-cors-proxy
git push heroku main

# ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ •
heroku config:set API_KEY=your_api_key_here

Vercel Serverless Functions ์‚ฌ์šฉํ•˜๊ธฐ

Vercel์€ ์„œ๋ฒ„๋ฆฌ์Šค ๋ฐฉ์‹์œผ๋กœ ํ”„๋ก์‹œ๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์–ด. ํŠธ๋ž˜ํ”ฝ์ด ์ ์„ ๋•Œ ๋น„์šฉ ํšจ์œจ์ ์ด์•ผ! ๐Ÿ’ฐ

// api/proxy.js (Vercel Functions)
const fetch = require('node-fetch');

module.exports = async (req, res) => {
  // CORS ํ—ค๋” ์„ค์ •
  res.setHeader('Access-Control-Allow-Origin', '*');
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
  
  if (req.method === 'OPTIONS') {
    return res.status(200).end();
  }
  
  try {
    // ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ์—์„œ ํƒ€๊ฒŸ URL ๊ฐ€์ ธ์˜ค๊ธฐ
    const { url } = req.query;
    
    if (!url) {
      return res.status(400).json({ error: 'URL ํŒŒ๋ผ๋ฏธํ„ฐ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค' });
    }
    
    // ์™ธ๋ถ€ API ํ˜ธ์ถœ
    const response = await fetch(url, {
      method: req.method,
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.API_KEY}`
      },
      body: req.method !== 'GET' ? JSON.stringify(req.body) : undefined
    });
    
    const data = await response.json();
    res.status(response.status).json(data);
    
  } catch (error) {
    console.error('Proxy error:', error);
    res.status(500).json({ error: error.message });
  }
};

ํด๋ผ์ด์–ธํŠธ์—์„œ ์‚ฌ์šฉ:

// ํด๋ผ์ด์–ธํŠธ ์ฝ”๋“œ
const targetUrl = encodeURIComponent('https://api.example.com/users');
fetch(`https://your-app.vercel.app/api/proxy?url=${targetUrl}`)
  .then(res => res.json())
  .then(data => console.log(data));

Docker๋กœ ์ปจํ…Œ์ด๋„ˆํ™”ํ•˜๊ธฐ

Docker๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์–ด๋””์„œ๋“  ๋™์ผํ•˜๊ฒŒ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ์–ด! ๐Ÿณ

# Dockerfile
FROM node:18-alpine

WORKDIR /app

# ์˜์กด์„ฑ ํŒŒ์ผ ๋ณต์‚ฌ
COPY package*.json ./

# ์˜์กด์„ฑ ์„ค์น˜
RUN npm ci --only=production

# ์†Œ์Šค ์ฝ”๋“œ ๋ณต์‚ฌ
COPY . .

# ํฌํŠธ ๋…ธ์ถœ
EXPOSE 5000

# ํ—ฌ์Šค์ฒดํฌ ์ถ”๊ฐ€
HEALTHCHECK --interval=30s --timeout=3s \
  CMD node healthcheck.js || exit 1

# ์‹คํ–‰
CMD ["node", "server.js"]
# docker-compose.yml
version: '3.8'

services:
  proxy:
    build: .
    ports:
      - "5000:5000"
    environment:
      - NODE_ENV=production
      - API_KEY=${API_KEY}
    restart: unless-stopped
    volumes:
      - ./logs:/app/logs
    networks:
      - proxy-network

networks:
  proxy-network:
    driver: bridge

์‹คํ–‰ ๋ฐฉ๋ฒ•:

# ์ด๋ฏธ์ง€ ๋นŒ๋“œ
docker build -t cors-proxy .

# ์ปจํ…Œ์ด๋„ˆ ์‹คํ–‰
docker run -p 5000:5000 -e API_KEY=your_key cors-proxy

# ๋˜๋Š” docker-compose ์‚ฌ์šฉ
docker-compose up -d

๐Ÿ”ง ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

โœ… ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ๋ฏผ๊ฐํ•œ ์ •๋ณด ๊ด€๋ฆฌ (.env ํŒŒ์ผ ์‚ฌ์šฉ)
โœ… HTTPS ์‚ฌ์šฉ (Let's Encrypt ๋“ฑ์œผ๋กœ SSL ์ธ์ฆ์„œ ์„ค์ •)
โœ… Rate Limiting ์ ์šฉ (DDoS ๊ณต๊ฒฉ ๋ฐฉ์ง€)
โœ… ๋กœ๊น… ์‹œ์Šคํ…œ ๊ตฌ์ถ• (์—๋Ÿฌ ์ถ”์  ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง)
โœ… ํ—ฌ์Šค์ฒดํฌ ์—”๋“œํฌ์ธํŠธ ์ถ”๊ฐ€ (/health)
โœ… ํ”„๋กœ์„ธ์Šค ๋งค๋‹ˆ์ € ์‚ฌ์šฉ (PM2, Forever ๋“ฑ)
โœ… ์ž๋™ ์žฌ์‹œ์ž‘ ์„ค์ •
โœ… ๋ฐฑ์—… ๋ฐ ๋ณต๊ตฌ ๊ณ„ํš ์ˆ˜๋ฆฝ

PM2๋กœ ํ”„๋กœ์„ธ์Šค ๊ด€๋ฆฌํ•˜๊ธฐ

์„œ๋ฒ„๊ฐ€ ์ฃฝ์œผ๋ฉด ์ž๋™์œผ๋กœ ์žฌ์‹œ์ž‘๋˜๊ฒŒ ๋งŒ๋“ค์ž! PM2๋Š” Node.js ํ”„๋กœ์„ธ์Šค ๋งค๋‹ˆ์ €์˜ ํ‘œ์ค€์ด์•ผ. ๐Ÿ”„

# PM2 ์„ค์น˜
npm install -g pm2

# ์„œ๋ฒ„ ์‹œ์ž‘
pm2 start server.js --name "cors-proxy"

# ํด๋Ÿฌ์Šคํ„ฐ ๋ชจ๋“œ (๋ฉ€ํ‹ฐ ์ฝ”์–ด ํ™œ์šฉ)
pm2 start server.js -i max --name "cors-proxy"

# ์ž๋™ ์žฌ์‹œ์ž‘ ์„ค์ •
pm2 startup
pm2 save

# ๋ชจ๋‹ˆํ„ฐ๋ง
pm2 monit

# ๋กœ๊ทธ ํ™•์ธ
pm2 logs cors-proxy

ecosystem.config.js ํŒŒ์ผ๋กœ ์„ค์ • ๊ด€๋ฆฌ:

// ecosystem.config.js
module.exports = {
  apps: [{
    name: 'cors-proxy',
    script: './server.js',
    instances: 'max',  // CPU ์ฝ”์–ด ์ˆ˜๋งŒํผ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ
    exec_mode: 'cluster',
    env: {
      NODE_ENV: 'production',
      PORT: 5000
    },
    error_file: './logs/err.log',
    out_file: './logs/out.log',
    log_date_format: 'YYYY-MM-DD HH:mm:ss',
    max_memory_restart: '500M',  // ๋ฉ”๋ชจ๋ฆฌ 500MB ์ดˆ๊ณผ ์‹œ ์žฌ์‹œ์ž‘
    watch: false,  // ํ”„๋กœ๋•์…˜์—์„œ๋Š” watch ๋น„ํ™œ์„ฑํ™”
    ignore_watch: ['node_modules', 'logs'],
    max_restarts: 10,
    min_uptime: '10s'
  }]
};
# ecosystem ํŒŒ์ผ๋กœ ์‹คํ–‰
pm2 start ecosystem.config.js

๐Ÿ› ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…: ์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋“ค

ํ”„๋ก์‹œ ์„œ๋ฒ„๋ฅผ ์šด์˜ํ•˜๋‹ค ๋ณด๋ฉด ์—ฌ๋Ÿฌ ๋ฌธ์ œ์— ๋ถ€๋”ชํžˆ๊ฒŒ ๋ผ. ์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ์ด์Šˆ๋“ค๊ณผ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์„ ์ •๋ฆฌํ•ด๋ดค์–ด! ๐Ÿ”ง

โŒ ๋ฌธ์ œ 1: ํ”„๋ก์‹œ ์„œ๋ฒ„๋Š” ์ž‘๋™ํ•˜๋Š”๋ฐ ์—ฌ์ „ํžˆ CORS ์—๋Ÿฌ

์›์ธ: ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์—ฌ์ „ํžˆ ์™ธ๋ถ€ API๋ฅผ ์ง์ ‘ ํ˜ธ์ถœํ•˜๊ณ  ์žˆ์„ ๊ฐ€๋Šฅ์„ฑ

ํ•ด๊ฒฐ:

// โŒ ์ž˜๋ชป๋œ ์ฝ”๋“œ - ์—ฌ์ „ํžˆ ์™ธ๋ถ€ API ์ง์ ‘ ํ˜ธ์ถœ
fetch('https://api.example.com/users')

// โœ… ์˜ฌ๋ฐ”๋ฅธ ์ฝ”๋“œ - ํ”„๋ก์‹œ ์„œ๋ฒ„๋ฅผ ํ†ตํ•ด ํ˜ธ์ถœ
fetch('http://localhost:5000/api/users')

โŒ ๋ฌธ์ œ 2: ํ”„๋ก์‹œ ์„œ๋ฒ„๊ฐ€ 504 Gateway Timeout ์—๋Ÿฌ

์›์ธ: ์™ธ๋ถ€ API ์‘๋‹ต์ด ๋„ˆ๋ฌด ๋А๋ฆฌ๊ฑฐ๋‚˜ ํƒ€์ž„์•„์›ƒ ์„ค์ •์ด ์งง์Œ

ํ•ด๊ฒฐ:

// ํƒ€์ž„์•„์›ƒ ์„ค์ • ๋Š˜๋ฆฌ๊ธฐ
app.use('/api', createProxyMiddleware({
  target: 'https://api.example.com',
  changeOrigin: true,
  timeout: 30000,  // 30์ดˆ๋กœ ์ฆ๊ฐ€
  proxyTimeout: 30000
}));

// ๋˜๋Š” fetch ์‚ฌ์šฉ ์‹œ
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), 30000);

fetch(url, { signal: controller.signal })
  .finally(() => clearTimeout(timeout));

โŒ ๋ฌธ์ œ 3: POST ์š”์ฒญ ์‹œ body๊ฐ€ undefined

์›์ธ: body-parser ๋ฏธ๋“ค์›จ์–ด๊ฐ€ ์—†๊ฑฐ๋‚˜ ์ˆœ์„œ๊ฐ€ ์ž˜๋ชป๋จ

ํ•ด๊ฒฐ:

const express = require('express');
const app = express();

// โœ… ํ”„๋ก์‹œ ๋ฏธ๋“ค์›จ์–ด๋ณด๋‹ค ๋จผ์ € ์„ค์ •!
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// ๊ทธ ๋‹ค์Œ ํ”„๋ก์‹œ ์„ค์ •
app.use('/api', createProxyMiddleware({...}));

โŒ ๋ฌธ์ œ 4: ์ฟ ํ‚ค๊ฐ€ ์ „๋‹ฌ๋˜์ง€ ์•Š์Œ

์›์ธ: ํฌ๋กœ์Šค ๋„๋ฉ”์ธ ์ฟ ํ‚ค ์„ค์ • ๋ฌธ์ œ

ํ•ด๊ฒฐ:

// ์„œ๋ฒ„ ์ธก
app.use(cors({
  origin: 'http://localhost:3000',
  credentials: true  // ์ฟ ํ‚ค ํ—ˆ์šฉ
}));

// ํด๋ผ์ด์–ธํŠธ ์ธก
fetch('http://localhost:5000/api/users', {
  credentials: 'include'  // ์ฟ ํ‚ค ํฌํ•จ
});

โŒ ๋ฌธ์ œ 5: ํ”„๋ก์‹œ ์„œ๋ฒ„ ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜

์›์ธ: ์บ์‹œ๋‚˜ ์ด๋ฒคํŠธ ๋ฆฌ์Šค๋„ˆ๊ฐ€ ์ œ๋Œ€๋กœ ์ •๋ฆฌ๋˜์ง€ ์•Š์Œ

ํ•ด๊ฒฐ:

// ์บ์‹œ TTL ์„ค์ •
const cache = new NodeCache({ 
  stdTTL: 600,  // 10๋ถ„
  checkperiod: 120,  // 2๋ถ„๋งˆ๋‹ค ๋งŒ๋ฃŒ๋œ ํ•ญ๋ชฉ ํ™•์ธ
  useClones: false  // ๋ฉ”๋ชจ๋ฆฌ ์ ˆ์•ฝ
});

// ์ฃผ๊ธฐ์ ์œผ๋กœ ์บ์‹œ ์ •๋ฆฌ
setInterval(() => {
  const keys = cache.keys();
  console.log(`ํ˜„์žฌ ์บ์‹œ ํ•ญ๋ชฉ ์ˆ˜: ${keys.length}`);
  
  // ์˜ค๋ž˜๋œ ํ•ญ๋ชฉ ์ˆ˜๋™ ์‚ญ์ œ
  if (keys.length > 1000) {
    cache.flushAll();
    console.log('์บ์‹œ ์ดˆ๊ธฐํ™”๋จ');
  }
}, 60000);  // 1๋ถ„๋งˆ๋‹ค

๐Ÿ“Š ์„ฑ๋Šฅ ์ตœ์ ํ™” ํŒ

ํ”„๋ก์‹œ ์„œ๋ฒ„์˜ ์„ฑ๋Šฅ์„ ๊ทน๋Œ€ํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•๋“ค์„ ์•Œ์•„๋ณด์ž! โšก

1๏ธโƒฃ ์••์ถ• ์‚ฌ์šฉํ•˜๊ธฐ

const compression = require('compression');
app.use(compression());

์‘๋‹ต ํฌ๊ธฐ๋ฅผ 70-90% ์ค„์ผ ์ˆ˜ ์žˆ์–ด!

2๏ธโƒฃ Keep-Alive ํ™œ์„ฑํ™”

const http = require('http');
const server = http.createServer(app);
server.keepAliveTimeout = 65000;

์—ฐ๊ฒฐ ์žฌ์‚ฌ์šฉ์œผ๋กœ ์†๋„ ํ–ฅ์ƒ!

3๏ธโƒฃ ํด๋Ÿฌ์Šคํ„ฐ ๋ชจ๋“œ

const cluster = require('cluster');
const numCPUs = require('os').cpus().length;

if (cluster.isMaster) {
  for (let i = 0; i < numCPUs; i++) {
    cluster.fork();
  }
} else {
  app.listen(5000);
}

๋ฉ€ํ‹ฐ ์ฝ”์–ด ํ™œ์šฉ์œผ๋กœ ์ฒ˜๋ฆฌ๋Ÿ‰ ์ฆ๊ฐ€!

4๏ธโƒฃ Redis ์บ์‹ฑ

const redis = require('redis');
const client = redis.createClient();

// Redis์— ์บ์‹œ ์ €์žฅ
client.setex(key, 3600, JSON.stringify(data));

๋ถ„์‚ฐ ์บ์‹ฑ์œผ๋กœ ํ™•์žฅ์„ฑ ํ™•๋ณด!

๐ŸŽ“ ์‹ค์ „ ์˜ˆ์ œ: ์™„์ „ํ•œ ํ”„๋ก์‹œ ์„œ๋ฒ„

์ง€๊ธˆ๊นŒ์ง€ ๋ฐฐ์šด ๋ชจ๋“  ๋‚ด์šฉ์„ ์ข…ํ•ฉํ•œ ์™„์ „ํ•œ ํ”„๋ก์‹œ ์„œ๋ฒ„ ์ฝ”๋“œ์•ผ! ์‹ค๋ฌด์—์„œ ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด. ๐Ÿ’ผ

// production-proxy-server.js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const cors = require('cors');
const helmet = require('helmet');  // ๋ณด์•ˆ ํ—ค๋”
const compression = require('compression');
const rateLimit = require('express-rate-limit');
const morgan = require('morgan');
const NodeCache = require('node-cache');
require('dotenv').config();

const app = express();
const PORT = process.env.PORT || 5000;

// ==================== ๋ณด์•ˆ ์„ค์ • ====================
app.use(helmet());  // ๋ณด์•ˆ ํ—ค๋” ์ž๋™ ์„ค์ •

// CORS ์„ค์ •
const allowedOrigins = process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'];
app.use(cors({
  origin: (origin, callback) => {
    if (!origin || allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('CORS ์ •์ฑ…์— ์˜ํ•ด ์ฐจ๋‹จ๋˜์—ˆ์Šต๋‹ˆ๋‹ค'));
    }
  },
  credentials: true
}));

// ==================== ๋ฏธ๋“ค์›จ์–ด ์„ค์ • ====================
app.use(compression());  // ์‘๋‹ต ์••์ถ•
app.use(express.json({ limit: '10mb' }));
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
app.use(morgan('combined'));  // ๋กœ๊น…

// ==================== Rate Limiting ====================
const limiter = rateLimit({
  windowMs: 15 * 60 * 1000,  // 15๋ถ„
  max: 100,  // ์ตœ๋Œ€ 100๊ฐœ ์š”์ฒญ
  message: { error: '๋„ˆ๋ฌด ๋งŽ์€ ์š”์ฒญ์ž…๋‹ˆ๋‹ค. ์ž ์‹œ ํ›„ ๋‹ค์‹œ ์‹œ๋„ํ•ด์ฃผ์„ธ์š”.' }
});
app.use('/api', limiter);

// ==================== ์บ์‹ฑ ์„ค์ • ====================
const cache = new NodeCache({ 
  stdTTL: 300,  // 5๋ถ„
  checkperiod: 60 
});

const cacheMiddleware = (req, res, next) => {
  if (req.method !== 'GET') return next();
  
  const key = req.originalUrl;
  const cachedData = cache.get(key);
  
  if (cachedData) {
    console.log(`โœ… ์บ์‹œ ํžˆํŠธ: ${key}`);
    return res.json(cachedData);
  }
  
  const originalJson = res.json.bind(res);
  res.json = (data) => {
    cache.set(key, data);
    return originalJson(data);
  };
  
  next();
};

// ==================== ํ—ฌ์Šค์ฒดํฌ ====================
app.get('/health', (req, res) => {
  res.json({
    status: 'healthy',
    uptime: process.uptime(),
    timestamp: new Date().toISOString(),
    cache: {
      keys: cache.keys().length,
      stats: cache.getStats()
    }
  });
});

// ==================== ์บ์‹œ ๊ด€๋ฆฌ API ====================
app.delete('/cache', (req, res) => {
  const authHeader = req.headers.authorization;
  if (authHeader !== `Bearer ${process.env.ADMIN_TOKEN}`) {
    return res.status(401).json({ error: '๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค' });
  }
  
  cache.flushAll();
  res.json({ message: '์บ์‹œ๊ฐ€ ์ดˆ๊ธฐํ™”๋˜์—ˆ์Šต๋‹ˆ๋‹ค' });
});

// ==================== ํ”„๋ก์‹œ ์„ค์ • ====================
app.use('/api', 
  cacheMiddleware,  // ์บ์‹ฑ ์ ์šฉ
  createProxyMiddleware({
    target: process.env.API_TARGET || 'https://api.example.com',
    changeOrigin: true,
    pathRewrite: { '^/api': '' },
    timeout: 30000,
    proxyTimeout: 30000,
    
    // ์š”์ฒญ ์ „ ์ฒ˜๋ฆฌ
    onProxyReq: (proxyReq, req, res) => {
      // API ํ‚ค ์ž๋™ ์ถ”๊ฐ€
      if (process.env.API_KEY) {
        proxyReq.setHeader('Authorization', `Bearer ${process.env.API_KEY}`);
      }
      
      console.log(`โžก๏ธ  ${req.method} ${req.path}`);
    },
    
    // ์‘๋‹ต ํ›„ ์ฒ˜๋ฆฌ
    onProxyRes: (proxyRes, req, res) => {
      console.log(`โฌ…๏ธ  ${proxyRes.statusCode} ${req.path}`);
    },
    
    // ์—๋Ÿฌ ์ฒ˜๋ฆฌ
    onError: (err, req, res) => {
      console.error('โŒ ํ”„๋ก์‹œ ์—๋Ÿฌ:', err.message);
      res.status(502).json({
        error: '์™ธ๋ถ€ API ํ˜ธ์ถœ ์‹คํŒจ',
        message: err.message,
        timestamp: new Date().toISOString()
      });
    }
  })
);

// ==================== 404 ์ฒ˜๋ฆฌ ====================
app.use((req, res) => {
  res.status(404).json({
    error: '์š”์ฒญํ•œ ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค',
    path: req.path
  });
});

// ==================== ์—๋Ÿฌ ํ•ธ๋“ค๋Ÿฌ ====================
app.use((err, req, res, next) => {
  console.error('์„œ๋ฒ„ ์—๋Ÿฌ:', err);
  res.status(500).json({
    error: '์„œ๋ฒ„ ๋‚ด๋ถ€ ์˜ค๋ฅ˜',
    message: process.env.NODE_ENV === 'development' ? err.message : undefined
  });
});

// ==================== ์„œ๋ฒ„ ์‹œ์ž‘ ====================
const server = app.listen(PORT, () => {
  console.log(`
๐Ÿš€ ํ”„๋ก์‹œ ์„œ๋ฒ„๊ฐ€ ์‹œ์ž‘๋˜์—ˆ์Šต๋‹ˆ๋‹ค!
๐Ÿ“ ํฌํŠธ: ${PORT}
๐ŸŒ ํ™˜๊ฒฝ: ${process.env.NODE_ENV || 'development'}
๐ŸŽฏ ํƒ€๊ฒŸ: ${process.env.API_TARGET || 'https://api.example.com'}
  `);
});

// ==================== Graceful Shutdown ====================
process.on('SIGTERM', () => {
  console.log('SIGTERM ์‹ ํ˜ธ ์ˆ˜์‹ , ์„œ๋ฒ„ ์ข…๋ฃŒ ์ค‘...');
  server.close(() => {
    console.log('์„œ๋ฒ„๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค');
    process.exit(0);
  });
});

process.on('SIGINT', () => {
  console.log('SIGINT ์‹ ํ˜ธ ์ˆ˜์‹ , ์„œ๋ฒ„ ์ข…๋ฃŒ ์ค‘...');
  server.close(() => {
    console.log('์„œ๋ฒ„๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์ข…๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค');
    process.exit(0);
  });
});

module.exports = app;  // ํ…Œ์ŠคํŠธ๋ฅผ ์œ„ํ•ด export

ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ • (.env):

# .env
NODE_ENV=production
PORT=5000
API_TARGET=https://api.example.com
API_KEY=your_secret_api_key_here
ADMIN_TOKEN=your_admin_token_here
ALLOWED_ORIGINS=https://yourdomain.com,https://www.yourdomain.com

๐Ÿงช ํ…Œ์ŠคํŠธ ์ฝ”๋“œ ์ž‘์„ฑํ•˜๊ธฐ

ํ”„๋ก์‹œ ์„œ๋ฒ„๋„ ํ…Œ์ŠคํŠธ๊ฐ€ ํ•„์š”ํ•ด! Jest์™€ Supertest๋กœ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•ด๋ณด์ž. ๐Ÿงช

// proxy-server.test.js
const request = require('supertest');
const app = require('./production-proxy-server');

describe('ํ”„๋ก์‹œ ์„œ๋ฒ„ ํ…Œ์ŠคํŠธ', () => {
  
  test('ํ—ฌ์Šค์ฒดํฌ ์—”๋“œํฌ์ธํŠธ', async () => {
    const response = await request(app).get('/health');
    
    expect(response.status).toBe(200);
    expect(response.body).toHaveProperty('status', 'healthy');
    expect(response.body).toHaveProperty('uptime');
  });
  
  test('์กด์žฌํ•˜์ง€ ์•Š๋Š” ๊ฒฝ๋กœ 404', async () => {
    const response = await request(app).get('/nonexistent');
    
    expect(response.status).toBe(404);
    expect(response.body).toHaveProperty('error');
  });
  
  test('Rate Limiting ํ…Œ์ŠคํŠธ', async () => {
    // 100๋ฒˆ ์ด์ƒ ์š”์ฒญ
    const requests = Array(101).fill().map(() => 
      request(app).get('/api/test')
    );
    
    const responses = await Promise.all(requests);
    const tooManyRequests = responses.filter(r => r.status === 429);
    
    expect(tooManyRequests.length).toBeGreaterThan(0);
  });
  
  test('์บ์‹ฑ ๋™์ž‘ ํ™•์ธ', async () => {
    const path = '/api/users';
    
    // ์ฒซ ๋ฒˆ์งธ ์š”์ฒญ
    const response1 = await request(app).get(path);
    
    // ๋‘ ๋ฒˆ์งธ ์š”์ฒญ (์บ์‹œ์—์„œ ๊ฐ€์ ธ์™€์•ผ ํ•จ)
    const response2 = await request(app).get(path);
    
    expect(response1.body).toEqual(response2.body);
  });
  
});

๐ŸŽฏ ์‹ค์ œ ์‚ฌ์šฉ ์‚ฌ๋ก€๋“ค

ํ”„๋ก์‹œ ์„œ๋ฒ„๊ฐ€ ์‹ค์ œ๋กœ ์–ด๋–ป๊ฒŒ ํ™œ์šฉ๋˜๋Š”์ง€ ๊ตฌ์ฒด์ ์ธ ์˜ˆ์‹œ๋ฅผ ์‚ดํŽด๋ณด์ž! ๐ŸŒŸ

์‚ฌ๋ก€ 1: ๋‚ ์”จ API ํ”„๋ก์‹œ

OpenWeatherMap API๋ฅผ ํ”„๋ก์‹œ๋กœ ๊ฐ์‹ธ์„œ API ํ‚ค๋ฅผ ์ˆจ๊ธฐ๊ณ  ์บ์‹ฑ ์ ์šฉ

// weather-proxy.js
app.get('/weather/:city', async (req, res) => {
  const { city } = req.params;
  const cacheKey = `weather_${city}`;
  
  // ์บ์‹œ ํ™•์ธ (10๋ถ„)
  const cached = cache.get(cacheKey);
  if (cached) {
    return res.json({ ...cached, cached: true });
  }
  
  try {
    const apiKey = process.env.OPENWEATHER_API_KEY;
    const url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${apiKey}&units=metric`;
    
    const response = await fetch(url);
    const data = await response.json();
    
    // ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋งŒ ์ถ”์ถœ
    const weatherData = {
      city: data.name,
      temperature: data.main.temp,
      description: data.weather[0].description,
      humidity: data.main.humidity,
      timestamp: new Date().toISOString()
    };
    
    cache.set(cacheKey, weatherData, 600);  // 10๋ถ„ ์บ์‹ฑ
    res.json(weatherData);
    
  } catch (error) {
    res.status(500).json({ error: '๋‚ ์”จ ์ •๋ณด๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค' });
  }
});

// ํด๋ผ์ด์–ธํŠธ ์‚ฌ์šฉ
fetch('http://localhost:5000/weather/Seoul')
  .then(res => res.json())
  .then(data => console.log(data));

์‚ฌ๋ก€ 2: ์ด๋ฏธ์ง€ ํ”„๋ก์‹œ & ๋ฆฌ์‚ฌ์ด์ง•

์™ธ๋ถ€ ์ด๋ฏธ์ง€๋ฅผ ํ”„๋ก์‹œํ•˜๋ฉด์„œ ๋ฆฌ์‚ฌ์ด์ง•๊นŒ์ง€ ์ฒ˜๋ฆฌ

// image-proxy.js
const sharp = require('sharp');  // npm install sharp

app.get('/image', async (req, res) => {
  const { url, width, height } = req.query;
  
  if (!url) {
    return res.status(400).json({ error: 'URL์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค' });
  }
  
  try {
    const response = await fetch(url);
    const buffer = await response.buffer();
    
    let image = sharp(buffer);
    
    // ๋ฆฌ์‚ฌ์ด์ง• ์˜ต์…˜
    if (width || height) {
      image = image.resize(
        width ? parseInt(width) : null,
        height ? parseInt(height) : null,
        { fit: 'inside' }
      );
    }
    
    // WebP๋กœ ๋ณ€ํ™˜ (์šฉ๋Ÿ‰ ์ ˆ์•ฝ)
    const optimized = await image.webp({ quality: 80 }).toBuffer();
    
    res.set('Content-Type', 'image/webp');
    res.set('Cache-Control', 'public, max-age=86400');  // 1์ผ ์บ์‹ฑ
    res.send(optimized);
    
  } catch (error) {
    res.status(500).json({ error: '์ด๋ฏธ์ง€ ์ฒ˜๋ฆฌ ์‹คํŒจ' });
  }
});

// ์‚ฌ์šฉ ์˜ˆ์‹œ
// <img src="http://localhost:5000/image?url=https://example.com/photo.jpg&width=300" />

์‚ฌ๋ก€ 3: GraphQL API ํ”„๋ก์‹œ

GraphQL ์ฟผ๋ฆฌ๋ฅผ ํ”„๋ก์‹œํ•˜๋ฉด์„œ ์ธ์ฆ ์ฒ˜๋ฆฌ

// graphql-proxy.js
app.post('/graphql', async (req, res) => {
  const { query, variables } = req.body;
  
  try {
    const response = await fetch('https://api.example.com/graphql', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${process.env.GRAPHQL_TOKEN}`
      },
      body: JSON.stringify({ query, variables })
    });
    
    const data = await response.json();
    
    // ์—๋Ÿฌ ๋กœ๊น…
    if (data.errors) {
      console.error('GraphQL ์—๋Ÿฌ:', data.errors);
    }
    
    res.json(data);
    
  } catch (error) {
    res.status(500).json({ 
      errors: [{ message: 'GraphQL ์š”์ฒญ ์‹คํŒจ' }] 
    });
  }
});

๐Ÿ” ๋ณด์•ˆ ๊ฐ•ํ™” ์ถ”๊ฐ€ ํŒ

ํ”„๋ก์‹œ ์„œ๋ฒ„์˜ ๋ณด์•ˆ์„ ๋”์šฑ ๊ฐ•ํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•๋“ค์ด์•ผ! ๐Ÿ›ก๏ธ

๐Ÿ”’ JWT ์ธ์ฆ

const jwt = require('jsonwebtoken');

const verifyToken = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  
  if (!token) {
    return res.status(401).json({ error: 'ํ† ํฐ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค' });
  }
  
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    res.status(401).json({ error: '์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ' });
  }
};

app.use('/api', verifyToken);

๐Ÿšซ IP ํ™”์ดํŠธ๋ฆฌ์ŠคํŠธ

const allowedIPs = process.env.ALLOWED_IPS?.split(',') || [];

const ipFilter = (req, res, next) => {
  const clientIP = req.ip || req.connection.remoteAddress;
  
  if (!allowedIPs.includes(clientIP)) {
    console.log(`์ฐจ๋‹จ๋œ IP: ${clientIP}`);
    return res.status(403).json({ error: '์ ‘๊ทผ ๊ฑฐ๋ถ€' });
  }
  
  next();
};

app.use('/api', ipFilter);

๐Ÿ”‘ API ํ‚ค ๊ฒ€์ฆ

const validateApiKey = (req, res, next) => {
  const apiKey = req.headers['x-api-key'];
  
  if (!apiKey || apiKey !== process.env.CLIENT_API_KEY) {
    return res.status(401).json({ error: 'API ํ‚ค๊ฐ€ ์œ ํšจํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค' });
  }
  
  next();
};

app.use('/api', validateApiKey);

๐Ÿ›ก๏ธ XSS ๋ฐฉ์ง€

const xss = require('xss-clean');
const mongoSanitize = require('express-mongo-sanitize');

app.use(xss());  // XSS ๊ณต๊ฒฉ ๋ฐฉ์ง€
app.use(mongoSanitize());  // NoSQL Injection ๋ฐฉ์ง€

๐Ÿ“š ๋งˆ๋ฌด๋ฆฌํ•˜๋ฉฐ

์™€! ์—ฌ๊ธฐ๊นŒ์ง€ ์ •๋ง ๊ธด ์—ฌ์ •์ด์—ˆ์–ด. ๐Ÿ˜… CORS๊ฐ€ ๋ญ”์ง€๋ถ€ํ„ฐ ์‹œ์ž‘ํ•ด์„œ ํ”„๋ก์‹œ ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค๊ณ , ๋ฐฐํฌํ•˜๊ณ , ์ตœ์ ํ™”ํ•˜๋Š” ๋ฐฉ๋ฒ•๊นŒ์ง€ ๋ชจ๋‘ ๋‹ค๋ค˜์–ด!

ํ•ต์‹ฌ๋งŒ ๋‹ค์‹œ ์ •๋ฆฌํ•˜๋ฉด:

๐ŸŽฏ ํ•ต์‹ฌ ์š”์•ฝ

1. CORS๋Š” ๋ธŒ๋ผ์šฐ์ € ๋ณด์•ˆ ์ •์ฑ…
๋‹ค๋ฅธ ์ถœ์ฒ˜์˜ ๋ฆฌ์†Œ์Šค ์ ‘๊ทผ์„ ์ œํ•œํ•˜๋Š” ๋ฉ”์ปค๋‹ˆ์ฆ˜์ด์•ผ.

2. ํ”„๋ก์‹œ ์„œ๋ฒ„๋Š” ์ค‘๊ฐœ์ž ์—ญํ• 
๋ธŒ๋ผ์šฐ์ €์™€ ์™ธ๋ถ€ API ์‚ฌ์ด์—์„œ ์š”์ฒญ์„ ๋Œ€์‹  ์ „๋‹ฌํ•ด์ค˜.

3. ๋‹ค์–‘ํ•œ ๊ตฌํ˜„ ๋ฐฉ๋ฒ•
http-proxy-middleware, ์ง์ ‘ ๊ตฌํ˜„, CRA ํ”„๋ก์‹œ, ์„œ๋ฒ„๋ฆฌ์Šค ๋“ฑ

4. ์ถ”๊ฐ€ ๊ธฐ๋Šฅ์œผ๋กœ ๊ฐ€์น˜ ์ƒ์Šน
์บ์‹ฑ, Rate Limiting, ์ธ์ฆ, ๋กœ๊น… ๋“ฑ์„ ์ถ”๊ฐ€ํ•˜๋ฉด ๋” ๊ฐ•๋ ฅํ•ด์ ธ.

5. ํ”„๋กœ๋•์…˜ ๋ฐฐํฌ ๊ณ ๋ ค์‚ฌํ•ญ
๋ณด์•ˆ, ์„ฑ๋Šฅ, ๋ชจ๋‹ˆํ„ฐ๋ง, ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ ๊ผผ๊ผผํžˆ ์ฑ™๊ฒจ์•ผ ํ•ด.

ํ”„๋ก์‹œ ์„œ๋ฒ„๋Š” ๋‹จ์ˆœํžˆ CORS ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๊ฒƒ ์ด์ƒ์˜ ๊ฐ€์น˜๊ฐ€ ์žˆ์–ด. API ํ‚ค ๋ณดํ˜ธ, ์„ฑ๋Šฅ ์ตœ์ ํ™”, ์š”์ฒญ ์ œ์–ด ๋“ฑ ๋‹ค์–‘ํ•œ ์ด์ ์„ ์ œ๊ณตํ•˜์ง€. ํŠนํžˆ ์žฌ๋Šฅ๋„ท ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ ์›น ๊ฐœ๋ฐœ ํ”„๋กœ์ ํŠธ๋ฅผ ์ง„ํ–‰ํ•  ๋•Œ, ์ด๋Ÿฐ ๊ธฐ์ˆ ์„ ํ™œ์šฉํ•˜๋ฉด ๋” ์•ˆ์ „ํ•˜๊ณ  ํšจ์œจ์ ์ธ ์„œ๋น„์Šค๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์–ด! ๐Ÿ’ช

์ฒ˜์Œ์—๋Š” ๋ณต์žกํ•ด ๋ณด์ผ ์ˆ˜ ์žˆ์ง€๋งŒ, ํ•˜๋‚˜์”ฉ ๋”ฐ๋ผ ํ•˜๋‹ค ๋ณด๋ฉด ๊ธˆ๋ฐฉ ์ต์ˆ™ํ•ด์งˆ ๊ฑฐ์•ผ. ์ด ๊ธ€์—์„œ ๋‹ค๋ฃฌ ์ฝ”๋“œ๋“ค์€ ๋ชจ๋‘ ์‹ค์ „์—์„œ ๋ฐ”๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒ€์ฆ๋œ ๋ฐฉ๋ฒ•๋“ค์ด๋‹ˆ๊นŒ, ์ž์‹  ์žˆ๊ฒŒ ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•ด๋ด! ๐Ÿš€

ํ˜น์‹œ ๋ง‰ํžˆ๋Š” ๋ถ€๋ถ„์ด ์žˆ๋‹ค๋ฉด, ๊ฐœ๋ฐœ์ž ์ปค๋ฎค๋‹ˆํ‹ฐ๋‚˜ ์žฌ๋Šฅ๋„ท์˜ ์ „๋ฌธ๊ฐ€๋“ค์—๊ฒŒ ๋„์›€์„ ์š”์ฒญํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์€ ๋ฐฉ๋ฒ•์ด์•ผ. ํ•จ๊ป˜ ์„ฑ์žฅํ•˜๋Š” ๊ฒŒ ๊ฐœ๋ฐœ์ž์˜ ๋ฉ‹์ง„ ๋ฌธํ™”๋‹ˆ๊นŒ! ๐Ÿ˜Š

๐ŸŽ“ ๋” ๊ณต๋ถ€ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด?

โœ… MDN Web Docs - CORS ๊ณต์‹ ๋ฌธ์„œ
โœ… Node.js ๊ณต์‹ ๋ฌธ์„œ - HTTP ๋ชจ๋“ˆ
โœ… Express.js ๋ฏธ๋“ค์›จ์–ด ๊ฐ€์ด๋“œ
โœ… Docker & Kubernetes ํ•™์Šต
โœ… AWS/GCP/Azure ํด๋ผ์šฐ๋“œ ์„œ๋น„์Šค
โœ… ๋ณด์•ˆ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค (OWASP)

์ž, ์ด์ œ ๋„ˆ๋„ CORS ๋งˆ์Šคํ„ฐ์•ผ! ๐ŸŽ‰ ์ž์‹ ๊ฐ์„ ๊ฐ€์ง€๊ณ  ๋ฉ‹์ง„ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค์–ด๋ด. ํ™”์ดํŒ…! ๐Ÿ’ชโœจ

ํ”„๋ก์‹œ ์„œ๋ฒ„๋กœ CORS ์ •๋ณต! ๐ŸŽฏ ๋ฌธ์ œ ํ•ด๊ฒฐ ๐Ÿš€ ์„ฑ๋Šฅ ํ–ฅ์ƒ ๐Ÿ” ๋ณด์•ˆ ๊ฐ•ํ™” Happy Coding! ์ฆ๊ฑฐ์šด ๊ฐœ๋ฐœ ๋˜์„ธ์š”! ๐Ÿ˜Š
๋Œ“๊ธ€ ์ž‘์„ฑ

์ด ๊ธ€์— ๋Œ€ํ•œ ์—ฌ๋Ÿฌ๋ถ„์˜ ์ƒ๊ฐ์„ ๋“ค๋ ค์ฃผ์„ธ์š”

๋Œ“๊ธ€ 0
๋Œ“๊ธ€์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ์ค‘์ž…๋‹ˆ๋‹ค.

์•„์ง ๋Œ“๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค.