์ƒ์„ธ ์ปจํ…์ธ 

๋ณธ๋ฌธ ์ œ๋ชฉ

[๋…ธ๋“œ 2ํŒ€] #11. ์›น API ์„œ๋ฒ„ ๋งŒ๋“ค๊ธฐ

24-25/Node.js 2

by sksmsyena 2025. 1. 10. 10:00

๋ณธ๋ฌธ

728x90

 

๐ŸŒŸํ‚ค์›Œ๋“œ: API ์„œ๋ฒ„, ํฌ๋กค๋ง, JWT ํ† ํฐ ์ธ์ฆ, ์‚ฌ์šฉ๋Ÿ‰ ์ œํ•œ, CORS

 


1. API ์„œ๋ฒ„ ์ดํ•ดํ•˜๊ธฐ

 

1. API์™€ ์›น API ์„œ๋ฒ„์˜ ๊ฐœ๋…

- API (Application Programming Interface): ๋‹ค๋ฅธ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ํ˜„์žฌ ํ”„๋กœ๊ทธ๋žจ์˜ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ—ˆ์šฉํ•˜๋Š” ์ ‘์ ์ด๋‹ค.

- ์›น API: ๋‹ค๋ฅธ ์›น ์„œ๋น„์Šค์˜ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์ž์›์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ๋Š” ์ฐฝ๊ตฌ์ด๋‹ค.

- ์›น API ์„œ๋ฒ„: ์„œ๋ฒ„์— API๋ฅผ ์˜ฌ๋ ค์„œ URL์„ ํ†ตํ•ด ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋งŒ๋“  ๊ฒƒ์ด๋‹ค.

 

2. ํฌ๋กค๋ง(crawling)

- ํฌ๋กค๋ง: ์›น ์‚ฌ์ดํŠธ๊ฐ€ ์ž์ฒด์ ์œผ๋กœ ์ œ๊ณตํ•˜๋Š” API๊ฐ€ ์—†๊ฑฐ๋‚˜ API ์ด์šฉ์— ์ œํ•œ์ด ์žˆ์„ ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ๋ฒ•์ด๋‹ค.

- ์›น ์‚ฌ์ดํŠธ์˜ ์ •๋ณด๋ฅผ ์ผ์ • ์ฃผ๊ธฐ๋กœ ์ˆ˜์ง‘ํ•ด ์ž์ฒด์ ์œผ๋กœ ๊ฐ€๊ณตํ•˜๋Š” ๊ธฐ์ˆ ์ด๋‹ค.

- ์›น ์‚ฌ์ดํŠธ์—์„œ ์ง์ ‘ ์ œ๊ณตํ•˜๋Š” API๊ฐ€ ์•„๋‹ˆ๋‹ค. → ์›ํ•˜๋Š” ์ •๋ณด๋ฅผ ์–ป์ง€ ๋ชปํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

- ๋„๋ฉ”์ธ/robots.txt์— ์ ‘์†ํ•˜์—ฌ ์›น ์‚ฌ์ดํŠธ๊ฐ€ ์–ด๋–ค ํŽ˜์ด์ง€์˜ ํฌ๋กค๋ง์„ ํ—ˆ์šฉํ•˜๋Š”์ง€ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

- ์ฃผ๊ธฐ์ ์ธ ํฌ๋กค๋ง=์›น ์„œ๋ฒ„์˜ ํŠธ๋ž˜ํ”ฝ์ด ์ฆ๊ฐ€ํ•ด ์„œ๋ฒ„์— ๋ฌด๋ฆฌ๊ฐ€ ๊ฐ„๋‹ค. → ๊ณต๊ฐœํ•ด๋„ ๋˜๋Š” ์ •๋ณด๋“ค์€ API๋กœ ๋งŒ๋“ค์–ด API๋ฅผ ํ†ตํ•ด ๊ฐ€์ ธ๊ฐ€๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

 

2. ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ ๊ฐ–์ถ”๊ธฐ

- NodeBird ์„œ๋น„์Šค์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ๊ณต์œ ํ•œ๋‹ค.

- ๋‹ค๋ฅธ ์„œ๋น„์Šค์—์„œ NodeBird ์„œ๋น„์Šค์˜ ๊ฒŒ์‹œ๊ธ€, ํ•ด์‹œํƒœ๊ทธ, ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ JSON ํ˜•์‹์œผ๋กœ ์ œ๊ณตํ•œ๋‹ค.

- ๋‹จ, ์ธ์ฆ๋ฐ›์€ ์‚ฌ์šฉ์ž๋งŒ ์ผ์ • ํ• ๋‹น๋Ÿ‰ ์•ˆ์—์„œ API ํ˜ธ์ถœ์„ ํ—ˆ์šฉํ•œ๋‹ค.

 

1. nodebird-api ํด๋”๋ฅผ ๋งŒ๋“  ํ›„ package.json ํŒŒ์ผ์„ ์ƒ์„ฑํ•œ๋‹ค.

2. (1) npm init์œผ๋กœ ์ƒ์„ฑํ•œ ํ›„ dependencies๋“ค์„ ์„ค์น˜ํ•˜๊ฑฐ๋‚˜ (2)) ์•„๋ž˜ pakage.json์„ ๋ณต์‚ฌํ•œ๋‹ค.

package.json

{
	"name": "nodebird-api",
	"version": "0.0.1",
	"description": "NodeBird API ์„œ๋ฒ„",
	"main": "app.js",
	"scripts": {
		"start": "nodemon app",
		"test": "echo \"Error: no test specified\" && exit 1"
    },
	"author": "Zero Cho",
	"license": "ISC",
	"dependencies": {
		"bcrypt": "^5.0.0",
		"cookie-parser": "^1.4.5",
		"dotenv": "^16.0.0",
		"express": "^4.17.1",
		"express-session": "A1.17.1",
		"morgan": "^1.10.0",
		"mysql2":, "^2.1.0",
		"nunjucks": "^3.2.1",
		"passport": "^0.5.2",
		"passport-kakao": "^1.0.1",
		"passport-local": "^1.0.0",
		"sequelize": "^6.0.0",
		"uuid":"^8.3.2"
	},
	"devDependencies": {
	"nodemon": "^2.0.3"
    }
   }

 

์ฝ˜์†”

$ npm i

- NodeBird์—์„œ config, models, passport, middle wares ํด๋”-์™€ ๋‚ด์šฉ๋ฌผ์„ ๋ณต์‚ฌํ•ด nodebird-api ํด๋”์— ๋ถ™์—ฌ ๋„ฃ๋Š”๋‹ค.

- controllers์™€ routes ํด๋”๋Š” auth.js๋งŒ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•œ๋‹ค.

- .env ํŒŒ์ผ์„ ๋ณต์‚ฌํ•˜๊ณ , ๋‹ค๋ฅธ ํด๋”์™€ ํŒŒ์ผ์€ ์ƒˆ๋กœ ๋‚˜์˜ฌ ๋•Œ๋งˆ๋‹ค ์ง์ ‘ ์ƒ์„ฑํ•œ๋‹ค.

 

3. ์—๋Ÿฌ ํ‘œ์‹œ ํŒŒ์ผ์„ ๋งŒ๋“ ๋‹ค.

- views ํด๋”๋ฅผ ๋งŒ๋“ค๊ณ  error.html ํŒŒ์ผ์„ ์ƒ์„ฑํ•œ๋‹ค.

 

4. ๋„๋ฉ”์ธ ๋ชจ๋ธ์„ ์ถ”๊ฐ€ํ•œ๋‹ค.

- ๋„๋ฉ”์ธ ๋ชจ๋ธ์—๋Š” ์ธํ„ฐ๋„ท ์ฃผ์†Œ, ๋„๋ฉ”์ธ ์ข…๋ฅ˜, ํด๋ผ์ด์–ธํŠธ ๋น„๋ฐ€ํ‚ค๊ฐ€ ๋“ค์–ด๊ฐ„๋‹ค.

- type ์ปฌ๋Ÿผ์€ ENUM ์†์„ฑ์œผ๋กœ ๋ฌด๋ฃŒ(free)๋‚˜ ํ”„๋ฆฌ๋ฏธ์—„(premium) ๋‘˜ ์ค‘ ํ•˜๋‚˜๋งŒ ๊ฐ’์œผ๋กœ ์ž…๋ ฅํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•œ๋‹ค.

- ํด๋ผ์ด์–ธํŠธ ๋น„๋ฐ€ ํ‚ค๋Š” ์š”์ฒญํ•œ ๋„๋ฉ”์ธ๊นŒ์ง€ ์ผ์น˜ํ•ด์•ผ ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๊ฒŒ ์ œํ•œ์„ ๋‘”๋‹ค.

- clientSecret ์ปฌ๋Ÿผ์€ ์ถฉ๋Œ ๊ฐ€๋Šฅ์„ฑ์ด ๋งค์šฐ ์ ์€ ๋žœ๋คํ•œ ๋ฌธ์ž์—ด์ธ UUID๋ผ๋Š” ํƒ€์ž…์„ ๊ฐ€์ง„๋‹ค.

- ๋„๋ฉ”์ธ ๋ชจ๋ธ์€ ์‚ฌ์šฉ์ž ๋ชจ๋ธ๊ณผ ์ผ๋Œ€๋‹ค ๊ด€๊ณ„๋ฅผ ๊ฐ–๋Š”๋ฐ, ์‚ฌ์šฉ์ž ํ•œ ๋ช…์ด ์—ฌ๋Ÿฌ ๋„๋ฉ”์ธ์„ ์†Œ์œ ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

5. ๋กœ๊ทธ์ธํ•˜๋Š” ํ™”๋ฉด์„ ๋งŒ๋“ ๋‹ค.

- ๋„๋ฉ”์ธ์„ ๋“ฑ๋กํ•˜๋Š” ํ™”๋ฉด๋„ ํฌํ•จ๋œ๋‹ค.

- ๋กœ๊ทธ์ธ์„ ํ•˜์ง€ ์•Š์•˜๋‹ค๋ฉด ๋กœ๊ทธ์ธ ์ฐฝ์ด ๋จผ์ € ๋œจ๊ณ , ๋กœ๊ทธ์ธํ•œ ์‚ฌ์šฉ์ž์—๊ฒ ๋„๋ฉ”์ธ ๋“ฑ๋ก ํ™”๋ฉด์„ ๋ณด์—ฌ์ค€๋‹ค.

 

6. ์„œ๋ฒ„ ์‹คํ–‰ํ•˜๊ธฐ

- http://localhost:8002๋กœ ์ ‘์†ํ•˜๋ฉด API๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ํ—ˆ๊ฐ€๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

http://localhost:8002 ์ ‘์† ํ™”๋ฉด

- ์‚ฌ์šฉ์ž ์ •๋ณด๋Š” NodeBird ์•ฑ๊ณผ ๊ณต์œ ํ•˜๋ฏ€๋กœ NodeBird ์•ฑ์˜ ์•„์ด๋””๋กœ ๋กœ๊ทธ์ธํ•˜๋ฉด ๋œ๋‹ค.

๋„๋ฉ”์ธ ๋“ฑ๋ก ํ™”๋ฉด

- ๋กœ๊ทธ์ธ ํ›„์—๋Š” ๋„๋ฉ”์ธ ๋“ฑ๋ก ํ™”๋ฉด์ด ๋‚˜ํƒ€๋‚œ๋‹ค.

- ์›น ๋ธŒ๋ผ์šฐ์ €์—์„œ ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ, ์‘๋‹ต์„ ํ•˜๋Š” ๊ณณ๊ณผ ๋„๋ฉ”์ธ์ด ๋‹ค๋ฅด๋ฉด CORS ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

๋„๋ฉ”์ธ ๋“ฑ๋ก ํ›„ ํ™”๋ฉด

-localhost:4000 ๋„๋ฉ”์ธ์„ ๋“ฑ๋กํ•˜์—ฌ ๋ฐœ๊ธ‰๋ฐ›์€ ๋น„๋ฐ€ ํ‚ค๋Š” NodeBird API๋ฅผ ํ˜ธ์ถœํ•  ๋•Œ ์ธ์ฆ ์šฉ๋„๋กœ ์‚ฌ์šฉํ•œ๋‹ค.

(์ž์„ธํ•œ ์ฝ”๋“œ๋Š” ์ฑ… ์ฐธ์กฐ...)

3. JWT ํ† ํฐ์œผ๋กœ ์ธ์ฆํ•˜๊ธฐ

1. JWT(JSON Web Token)

- JWT: JSON ํ˜•์‹์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š” ํ† ํฐ์ด๋‹ค.

- ๋‚ด์šฉ์„ ๋ณผ ์ˆ˜ ์žˆ๊ธฐ์— JWT์—๋Š” ๋ฏผ๊ฐํ•œ ๋‚ด์šฉ์„ ๋„ฃ์œผ๋ฉด ์•ˆ๋œ๋‹ค.

- JWT๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์„ธ ๋ถ€๋ถ„์œผ๋กœ ๊ตฌ์„ฑ๋˜์–ด์žˆ๋‹ค.

โ‘  ํ—ค๋”

- ํ† ํฐ ์ข…๋ฅ˜์™€ ํ•ด์‹œ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ์ •๋ณด๊ฐ€ ๋“ค์–ด์žˆ๋‹ค.

โ‘ก ํŽ˜์ด๋กœ๋“œ

- ํ† ํฐ์˜ ๋‚ด์šฉ๋ฌผ์ด ์ธ์ฝ”๋”ฉ๋œ ๋ถ€๋ถ„์ด๋‹ค.

โ‘ข ์‹œ๊ทธ๋‹ˆ์ฒ˜

- ์ผ๋ จ์˜ ๋ฌธ์ž์—ด, ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ํ†ตํ•ด ํ† ํฐ์˜ ๋ณ€์กฐ ์—ฌ๋ถ€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

- JWT ๋น„๋ฐ€ํ‚ค๋กœ ๋งŒ๋“ค์–ด์ง€๋ฉฐ, ๋…ธ์ถœ ์‹œ์— ํ† ํฐ์ด ์œ„์กฐ๋  ์ˆ˜ ์žˆ๋‹ค.

- ์‹œ๊ทธ๋‹ˆ์ฒ˜ ์ž์ฒด๋Š” ์ˆจ๊ธฐ์ง€ ์•Š์•„๋„ ๋œ๋‹ค.

 

2. ํ† ํฐ์„ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ 

โ‘  ํ† ํฐ ๋‚ด์šฉ ํ™•์ธํ•˜๊ธฐ (https://jwt.io)

- ์šฐ์ธก ํ•˜๋‹จ์˜ serect ๋ถ€๋ถ„์ด JWT ๋น„๋ฐ€ ํ‚ค ์ด๋‹ค. 

- ํŽ˜์ด๋กœ๋“œ ๋ถ€๋ถ„์ด ๋…ธ์ถœ๋˜์–ด ๋‚ด์šฉ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

 

โ‘ก ๋‚ด์šฉ์ด ๋…ธ์ถœ๋˜๋Š” JWT ํ† ํฐ์„ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ 

- ๋‚ด์šฉ์ด ์—†๋Š” ๋žœ๋คํ•œ ํ† ํฐ์„ ์‚ฌ์šฉํ•  ๊ฒฝ์šฐ: ํ† ํฐ์˜ ์ฃผ์ธ, ๊ถŒํ•œ์„ ์š”์ฒญ๋งˆ๋‹ค ์ฒดํฌํ•ด์•ผํ•œ๋‹ค.

- JWT ํ† ํฐ์€ JWT ๋น„๋ฐ€ ํ‚ค๋ฅผ ์•Œ์ง€ ์•Š๋Š” ์ด์ƒ ๋ณ€์กฐ๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค.

- ๋ณ€์กฐํ•œ ํ† ํฐ์€ ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฅผ ๋น„๋ฐ€ ํ‚ค๋ฅผ ํ†ตํ•ด ๊ฒ€์‚ฌํ•  ๋•Œ ๊ตฌ๋ณ„ํ•  ์ˆ˜ ์žˆ๋‹ค. 

→ ์™ธ๋ถ€์— ๋…ธ์ถœ๋˜์–ด๋„ ์ข‹์€ ์ •๋ณด์— ํ•œ ํ•ด ์‚ฌ์šฉ์ž ์ด๋ฆ„, ๊ถŒํ•œ์„ ๋„ฃ๊ณ  ์•ˆ์‹ฌํ•˜๋ฉฐ ๋‚ด์šฉ๋ฌผ์„ ๋ฏฟ๊ณ  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์กฐํšŒ ์—†์ด๋„ ๊ถŒํ•œ์„ ์ค„ ์ˆ˜ ์žˆ๋‹ค. 

 

3. JWT ํ† ํฐ์˜ ๋‹จ์ 

- ๋‚ด์šฉ๋ฌผ์ด ๋“ค์–ด์žˆ์–ด ๋žœ๋คํ•œ ํ† ํฐ์„ ์‚ฌ์šฉํ•  ๋•Œ๋ณด๋‹ค ์šฉ๋Ÿ‰์ด ํฌ๋‹ค.

- ์š”์ฒญ ๋•Œ๋งˆ๋‹ค ํ† ํฐ์ด ์˜ค๊ฐ€์„œ ๋ฐ์ดํ„ฐ ์–‘์ด ์ฆ๊ฐ€ํ•œ๋‹ค.

→ ๋žœ๋คํ•œ ๋ฌธ์ž์—ด์„ ์‚ฌ์šฉํ•ด์„œ ๋งค๋ฒˆ ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ์กฐํšŒํ•˜๋Š” ์ž‘์—…์˜ ๋น„์šฉ๊ณผ JWT ํ† ํฐ์„ ์‚ฌ์šฉํ•ด์„œ ๋ฐœ์ƒํ•˜๋Š” ๋ฐ์ดํ„ฐ ๋น„์šฉ์„ ๋น„๊ตํ•˜์—ฌ ์ ์ ˆํ•œ ๋•Œ์— ์‚ฌ์šฉํ•˜๋ฉด ์ข‹๋‹ค.

  

4. ์›น ์„œ๋ฒ„์—์„œ JWT ํ† ํฐ ์ธ์ฆ ๊ณผ์ • ๊ตฌํ˜„ํ•˜๊ธฐ

โ‘  JWT ๋ชจ๋“ˆ ์„ค์น˜ํ•˜๊ธฐ

์ฝ˜์†”

$ npm i jsonwebtoken

 

โ‘ก JWT๋ฅผ ์‚ฌ์šฉํ•ด์„œ API ๋งŒ๋“ค๊ธฐ

- ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž๊ฐ€ API๋ฅผ ์“ฐ๋ ค๋ฉด JWT ํ† ํฐ์„ ๋ฐœ๊ธ‰ํ•˜๊ณ  ์ธ์ฆ์„ ๋ฐ›์•„์•ผํ•œ๋‹ค.

→ ๋ผ์šฐํ„ฐ์— ๊ณตํ†ต๋˜๋ฏ€๋กœ ๋ฏธ๋“ค์›จ์–ด๋กœ ๋งŒ๋“ค์–ด ๋‘๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

nodebird-api/.env

COOKIE_SECRET=cookiesecret
KAKAO_ID=5d4daf57becfd72fd9c919882552c4a6
JWT_SECRET=jwtSecret

 

nodebird-api/middlewares/index.js

const jwt = require('jsonwebtoken');
...
exports.verifyToken = (req, res, next) => {
	try {
    //์ธ์ฆ ์„ฑ๊ณต ์‹œ์—๋Š” ํ† ํฐ์˜ ๋‚ด์šฉ์ด ๋ฐ˜ํ™˜๋˜์–ด res.locals.decoded์— ์ €์žฅ
		res.locals.decoded = jwt.verify(req.headers.authorization, process.env.JWT_SECRET);
	// jwt.verify ๋ฉ”์„œ๋“œ๋กœ ํ† ํฐ์„ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๋‹ค.
	// ์ฒซ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ํ† ํฐ, ๋‘ ๋ฒˆ์งธ ์ธ์ˆ˜๋กœ ํ† ํฐ์˜ ๋น„๋ฐ€ ํ‚ค๋ฅผ ๋„ฃ๋Š”๋‹ค.
		return next();
	} catch (error) { //
		if (error.name === 'TokenExpiredError') { // ์œ ํšจ ๊ธฐ๊ฐ„ ์ดˆ๊ณผ๋œ ๊ฒฝ์šฐ
			return res.status(419).json({
				code: 419, //์ฝ”๋“œ๋Š” 400๋ฒˆ ๋Œ€ ์ˆซ์ž ์ค‘์—์„œ ๋งˆ์Œ๋Œ€๋กœ ์„ ํƒ ๊ฐ€๋Šฅ
				message: 'ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค',
			});
		}
		return res.status(401).json({ // ํ† ํฐ์˜ ๋น„๋ฐ€ํ‚ค๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ
			code: 401,
			message: '์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ์ž…๋‹ˆ๋‹ค',
		});
	}
};

- res.locals์— ์‚ฌ์šฉ์ž ์•„์ด๋””, ๋‹‰๋„ค์ž„, ๋ฐœ๊ธ‰์ž ์œ ํšจ๊ธฐ๊ฐ„ ๋“ฑ์„ ์ €์žฅํ–ˆ์œผ๋ฏ€๋กœ ๋‹ค์Œ ๋ฏธ๋“ค์›จ์–ด์—์„œ ํ† ํฐ์˜ ๋‚ด์šฉ๋ฌผ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.


nodebird-api/routes/v1.js

const express = require('express');

const { verifyToken } = require('../middlewares');
const { createToken, tokenTest } = require('../controllers/v1');

const router = express.Router();

// POST /v1/token: ํ† ํฐ์„ ๋ฐœ๊ธ‰ํ•˜๋Š” ๋ผ์šฐํ„ฐ
router.post('/token', createToken);

// GET /v1/test: ์‚ฌ์šฉ์ž๊ฐ€ ํ† ํฐ์„ ํ…Œ์ŠคํŠธํ•ด๋ณผ ์ˆ˜ ์žˆ๋Š” ๋ผ์šฐํ„ฐ
router.get('/test', verifyToken, tokenTest);

module.exports = router;

- ๋ผ์šฐํ„ฐ ์ด๋ฆ„/๋ฒ„์ „(v1): ํ•œ ๋ฒˆ ๋ฒ„์ „์ด ์ •ํ•ด์ง„ ํ›„์—๋Š” ๋ผ์šฐํ„ฐ๋ฅผ ํ•จ๋ถ€๋กœ ์ˆ˜์ •ํ•˜๋ฉด ์•ˆ๋œ๋‹ค.

- ๊ธฐ์กด์— ์žˆ๋˜ ๋ผ์šฐํ„ฐ๊ฐ€ ์ˆ˜์ •๋˜๋ฉด API๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํ”„๋กœ๊ทธ๋žจ๋“ค์ด ์˜ค์ž‘๋™ํ•  ์ˆ˜ ์žˆ๋‹ค.

→ ๊ธฐ์กด ์‚ฌ์šฉ์ž์—๊ฒŒ ์˜ํ–ฅ์ด ๋ฏธ์น  ์ •๋„๋กœ ์ˆ˜์ •ํ•ด์•ผ ํ•œ๋‹ค๋ฉด, ๋ฒ„์ „์„ ์˜ฌ๋ฆฐ ๋ผ์šฐํ„ฐ๋ฅผ ์ƒˆ๋กœ ์ถ”๊ฐ€ํ•œ ๋’ค ์ƒˆ๋กœ์šด API๊ฐ€ ๋‚˜์™”์Œ์„ ์•Œ๋ฆฐ๋‹ค.

- ๋ฒ„์ „์„ ๋ฐ˜๋“œ์‹œ ๋ผ์šฐํ„ฐ์— ํ‘œ์‹œํ•  ํ•„์š”๋Š” ์—†๋‹ค. (ํ—ค๋”์— ๋ฒ„์ „ ํ‘œ์‹œ, ์ฟผ๋ฆฌ์ŠคํŠธ๋ง/๋ณธ๋ฌธ์— ๋ฒ„์ „ ํ‘œ์‹œ ๊ฐ€๋Šฅ)

 


nodebird-api/controllers/v1.js

const jwt = require('jsonwebtoken');
const { Domain, User } = require('../models');

exports.createToken = async (req, res) => {
	const { clientSecret } = req.body;
		try {
			const domain = await Domain.findOne({
				where: { clientSecret },
				include: {
					model: User,
					attribute: ['nick', 'id'],
				},
			});
		if (!domain) {
			return res.status(401).json({
				code: 401,
				message: '๋“ฑ๋ก๋˜์ง€ ์•Š์€ ๋„๋ฉ”์ธ์ž…๋‹ˆ๋‹ค. ๋จผ์ € ๋„๋ฉ”์ธ์„ ๋“ฑ๋กํ•˜์„ธ์š”',
			});
		}
		const token = jwt.sign({ //sign์˜ ์ฒซ๋ฒˆ์งธ ์ธ์ˆ˜: ํ† ํฐ์˜ ๋‚ด์šฉ (์‚ฌ์šฉ์ž ์•„์ด๋””, ๋‹‰๋„ค์ž„)
			id: domain.User.id,
			nick: domain.User.nick,
		}, process.env.JWT_SECRET, { //sign ๋‘๋ฒˆ์งธ ์ธ์ˆ˜: ํ† ํฐ์˜ ๋น„๋ฐ€ํ‚ค
			expiresIn: '1m', // 1๋ถ„ //sign ์„ธ๋ฒˆ์งธ ์ธ์ˆ˜: ํ† ํฐ์˜ ์„ค์ •(์œ ํšจ๊ธฐ๊ฐ„, ๋ฐœ๊ธ‰์ž)
			issuer: 'nodebird',
		});
		return res.json({ //์ผ์ •ํ•œ ํ˜•์‹์œผ๋กœ ๊ฐ–์ถฐ์•ผ ์‘๋‹ต๋ฐ›๋Š” ์ชฝ์—์„œ ์ฒ˜๋ฆฌํ•˜๊ธฐ ์ข‹์Œ
			code: 200, //์ฝ”๋“œ๊ฐ€ 200๋ฒˆ๋Œ€๊ฐ€ ์•„๋‹ˆ๋ฉด ์—๋Ÿฌ
			message: 'ํ† ํฐ์ด ๋ฐœ๊ธ‰๋˜์—ˆ์Šต๋‹ˆ๋‹ค', //code๋ฅผ ์ดํ•ดํ•˜์ง€ ๋ชปํ•  ๊ฒฝ์šฐ๋ฅผ ๋Œ€๋น„
			token,
		});
	} catch (error) {
		console.error(error);
		return res.status(500).json({
			code: 500,
			message: '์„œ๋ฒ„ ์—๋Ÿฌ',
		});
	}
};

exports.tokenTest = (req, res) => {
res.json(res.locals.decoded);
};

 

- ๋ผ์šฐํ„ฐ๋ฅผ ์„œ๋ฒ„์— ์—ฐ๊ฒฐํ•œ๋‹ค.

nodebird-api/app.js

...
dotenv.config();
const v1=require('./routes/v1');
const authRouter=require('./routes/auth');
...
app.use(passport.sessing());

app.use('/v1', v1);
app.use('/auth', authRouter);
...

 

4. ๋‹ค๋ฅธ ์„œ๋น„์Šค์—์„œ ํ˜ธ์ถœํ•˜๊ธฐ

- API ์‚ฌ์šฉํ•˜๋Š” ์„œ๋น„์Šค: ๋‹ค๋ฅธ ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ด๋ฏ€๋กœ ํด๋ผ์ด์–ธํŠธ ์—ญํ• ์„ ํ•œ๋‹ค.

- NodeBird ์•ฑ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ  ์‹ถ์–ดํ•˜๋Š” ์‚ฌ์šฉ์ž= ๋ณดํ†ต ๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๊ณตํ•ด 2์ฐจ์ ์ธ ์„œ๋น„์Šค๋ฅผ ํ•˜๋ ค๋Š” ํšŒ์‚ฌ๊ฐ€ API๋ฅผ ์ด์šฉํ•œ๋‹ค.

(์˜ˆ์‹œ) ์‡ผํ•‘๋ชฐ๋“ค์˜ ์ตœ์ €๊ฐ€๋ฅผ ์•Œ๋ ค์ฃผ๋Š” ์„œ๋น„์Šค๊ฐ€ 2์ฐจ ์„œ๋น„์Šค๊ฐ€ ๋œ๋‹ค.

- 2์ฐจ ์„œ๋น„์Šค ์ด๋ฆ„์„ NodeCat์œผ๋กœ ํ•œ๋‹ค.

โ‘  nodebird-api์˜ API๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์ด ์ฃผ๋ชฉ์ ์ธ ์„œ๋ฒ„

nodecat/package.json

{
	"name": "nodecat",
	"version": "0.0.1",
	"description": "NodeBird 2์ฐจ ์„œ๋น„์Šค",
	"main": "app.js",
	"scripts": {
		"start": "nodemon app"
	},
	"author": "Zero Cho",
	"license": "ISC",
	"dependencies": {
		"axios": "^0.27.2",
		"cookie-parser": "^1.4.6",
		"dotenv": "^16.0.1",
		"express": "^4.18.1",
		"express-session": "^1.17.3",
		"morgan": "^1.10.0",
		"nunjucks": "^3.2.3"
	},
	"devDependencies": {
		"nodemon": "^2.0.16"
	}
}

 

์ฝ˜์†”

$ npm i

 

 

 

โ‘ก ์„œ๋ฒ„ํŒŒ์ผ๊ณผ ์—๋Ÿฌ๋ฅผ ํ‘œ์‹œํ•  ํŒŒ์ผ ์ƒ์„ฑ

nodecat/app.js

const express = require('express');
const morgan = require('morgan');
const cookieParser = require('cookie-parser');
const session = require('express-session');
const nunjucks = require('nunjucks');
const dotenv = require('dotenv');

dotenv.config();
const indexRouter = require('./routes');

const app = express();
app.set('port', process.env.PORT || 4000);
app.set('view engine', 'html');
nunjucks.configure('views', {
	express: app,
	watch: true,
	});

app.use(morgan('dev'));
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(session({
	resave: false,
	saveUninitialized: false,
	secret: process.env.COOKIE_SECRET,
	cookie: {
		httpOnly: true,
		secure: false,
	},
}));

app.use('/', indexRouter);

app.use((req, res, next) => {
	const error = new Error(`${req.method} ${req.url} ๋ผ์šฐํ„ฐ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.`);
	error.status = 404;
	next(error);
});

app.use((err, req, res, next) => {
	res.locals.message = err.message;
	res.locals.error = process.env.NODE_ENV !== 'production' ? err : {};
	res.status(err.status || 500);
	res.render('error');
});

app.listen(app.get('port'), () => {
	console.log(app.get('port'), '๋ฒˆ ํฌํŠธ์—์„œ ๋Œ€๊ธฐ ์ค‘');
});

 

 

nodecat/error.html

<h1>{{message}}</h1>
<h2>{{error.status}}</h2>
<pre>{{error.stack}}</pre>

 

โ‘ข ์‚ฌ์šฉ์ž ์ธ์ฆ ์ง„ํ–‰์„ ํ…Œ์ŠคํŠธํ•˜๋Š” ๋ผ์šฐํ„ฐ ๋งŒ๋“ค๊ธฐ

- clientSecret์„ .env

nodecat/.env

COOKIE_SECRET=nodecat
CLIENT_SECRET=7d67444e-fd01-4f9b-8680-f72464d02a57

 

nodecat/routes/index.js

const express = require('express');
const { test } = require('../controllers');

const router = express.Router();

// POST /test
router.get('/test', test);

module.exports = router;

 

 

nodecat/controllers/index.js

const axios = require('axios');

exports.test = async (req, res, next) => { // ํ† ํฐ ํ…Œ์ŠคํŠธ ๋ผ์šฐํ„ฐ
	try {
		if (!req.session.jwt) { // ์„ธ์…˜์— ํ† ํฐ์ด ์—†์œผ๋ฉด ํ† ํฐ ๋ฐœ๊ธ‰ ์‹œ๋„
			const tokenResult = await axios.post('http://localhost:8002/v1/token', {
				clientSecret: process.env.CLIENT_SECRET,
			});
		if (tokenResult.data?.code === 200) { // ํ† ํฐ ๋ฐœ๊ธ‰ ์„ฑ๊ณต
			req.session.jwt = tokenResult.data.token; // ์„ธ์…˜์— ํ† ํฐ ์ €์žฅ
		} else { // ํ† ํฐ ๋ฐœ๊ธ‰ ์‹คํŒจ
			return res.json(tokenResult.data); // ๋ฐœ๊ธ‰ ์‹คํŒจ ์‚ฌ์œ  ์‘๋‹ต
		}
	}
// ๋ฐœ๊ธ‰๋ฐ›์€ ํ† ํฐ ํ…Œ์ŠคํŠธ
	const result = await axios.get('http://localhost:8002/v1/test', {
		headers: { authorization: req.session.jwt },// ๋ฐœ๊ธ‰๋ฐ›์€ ํ† ํฐ์œผ๋กœ ํ† ํฐ์ด ์œ ํšจํ•œ์ง€ ํ…Œ์ŠคํŠธ
	});
	return res.json(result.data);
} catch (error) {
	console.error(error);
	if (error.response?.status === 419) { // ํ† ํฐ ๋งŒ๋ฃŒ ์‹œ
		return res.json(error.response.data);
	}
	return next(error);
	}
};

 

- GET/test ๋ผ์šฐํ„ฐ ์‚ฌ์šฉํ•˜๊ธฐ

- ์ฝ˜์†”์„ ํ•˜๋‚˜ ๋” ๋„์›Œ ์„œ๋ฒ„(localhost:4000)์™€ nodebird-api(localhost:8002)๋„ ์‹คํ–‰ ์ค‘์ด์–ด์•ผํ•œ๋‹ค.

 

NodeCat๊ณผ NodeBird API ์„œ๋ฒ„์—์„œ ๋ชจ๋‘ ์‹คํ–‰

$ npm start
//๊ฐ๊ฐ ๋‹ค๋ฅธ ํฌํŠธ์—์„œ ๋Œ€๊ธฐ ์ค‘
4000 ๋ฒˆ ํฌํŠธ์—์„œ ๋Œ€๊ธฐ ์ค‘
8002๋ฒˆ ํฌํŠธ์—์„œ ๋Œ€๊ธฐ ์ค‘

- localhost:8002๋Š” API ์„œ๋น„์Šค๋ฅผ ์ œ๊ณตํ•˜๋Š” nodebird-api ์„œ๋ฒ„

- localhost:4000์€ API ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋Š” NodeCat ์„œ๋ฒ„

 

- ์ ‘์†ํ•˜๋ฉด ๋‹ค์Œ๊ณผ ๊ฐ™์ด ๋ฐœ๊ธ‰๋ฐ›์€ ํ† ํฐ์˜ ๋‚ด์šฉ์ด ํ‘œ์‹œ๋œ๋‹ค.

- 1๋ถ„์„ ๊ธฐ๋‹ค๋ฆฐ ํ›„ ๋‹ค์‹œ https://localhost:4000/test ๋ผ์šฐํ„ฐ์— ์ ‘์†ํ•˜๋ฉด ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์—ˆ๋‹ค๋Š” ๋ฉ”์‹œ์ง€๊ฐ€ ๋‚˜ํƒ€๋‚œ๋‹ค.

5. SNS API ์„œ๋ฒ„ ๋งŒ๋“ค๊ธฐ

- ๋‹ค์‹œ API ์ œ๊ณต์ž(nodebird-api)์˜ ์ž…์žฅ์œผ๋กœ ๋Œ์•„์™€์„œ ๋‚˜๋จธ์ง€ API ๋ผ์šฐํ„ฐ๋ฅผ ์™„์„ฑํ•œ๋‹ค.

 

nodebird-api/routes/v1.js

const express = require('express');

const { verifyToken } = require('../middlewares');
const { createToken, tokenTest, getMyPosts, getPostsByHashtag } = require('../controllers/v1');

const router = express.Router();
...

// GET /v1/posts/my
router.get('/posts/my', verifyToken, getMyPosts);

// GET /v1/posts/hashtag/:title
router.get('/posts/hashtag/:title', verifyToken, getPostsByHashtag);

module.exports = router;

- GET/v1/posts/my์™€ GET /v1/posts/hashtag/:title์€ ๋‚ด๊ฐ€ ์˜ฌ๋ฆฐ ํฌ์ŠคํŠธ์™€ ํ•ด์‹œํƒœํฌ ๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ๋ผ์šฐํ„ฐ

 

nodebird-api/controllers/v1.js 

const jwt = require('jsonwebtoken');
const { Domain, User, Post, Hashtag } = require('../models');
...
exports.tokenTest = (req, res) => {
	res.json(res.locals.decoded);
};

exports.getMyPosts = (req, res) => {
	Post.findAll({ where: { userId: res.locals.decoded.id } })
	.then((posts) => {
		console.log(posts);
		res.json({
			code: 200,
			payload: posts,
		});
	})
	.catch((error) => {
		console.error(error);
		return res.status(500).json({
			code: 500,
			message: '์„œ๋ฒ„ ์—๋Ÿฌ',
		});
	});
};

exports.getPostsByHashtag = async (req, res) => {
	try {
		const hashtag = await Hashtag.findOne({ where: { title: req.params.title } });
		if (!hashtag) {
			return res.status(404).json({
				code: 404,
				message: '๊ฒ€์ƒ‰ ๊ฒฐ๊ณผ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค',
			});
		}
		const posts = await hashtag.getPosts();
		return res.json({
			code:200,
			payload:posts,
		});
	} catch(error){
		console.error(error);
		return res.status(500).json({
			code:500,
			message:'์„œ๋ฒ„ ์—๋Ÿฌ',
		});
	}
};

 

 

- ์‚ฌ์šฉํ•˜๋Š” ์ธก(NodeCat)์—์„œ๋Š” ์œ„์˜ API๋ฅผ ์ด์šฉํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

- ํ† ํฐ์„ ๋ฐœ๊ธ‰๋ฐ›๋Š” ๋ถ€๋ถ„์€ ๋ฐ˜๋ณต๋˜๋ฏ€๋กœ ํ•จ์ˆ˜๋กœ ๋งŒ๋“ค์–ด ์žฌ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค. 

nodecat/routes/index.js

const express = require('express');

const { searchByHashtag, getMyPosts } = require('../controllers');

const router = express.Router();

router.get('/myposts', getMyPosts);

router.get('/search/:hashtag', searchByHashtag);

module.exports = router;

nodecat/.env

API_URL=http://localhost:8002/v1
ORIGIN=http://localhost:4000

nodecat/controllers/index.js

const axios = require('axios');
const URL = process.env.API_URL;

axios.defaults.headers.origin = process.env.ORIGIN; // origin ํ—ค๋” ์ถ”๊ฐ€
//(1)
const request = async (req, api) => {
  try {
    if (!req.session.jwt) { // ์„ธ์…˜์— ํ† ํฐ์ด ์—†์œผ๋ฉด
      const tokenResult = await axios.post(`${URL}/token`, {
        clientSecret: process.env.CLIENT_SECRET,
      });
      req.session.jwt = tokenResult.data.token; // ์„ธ์…˜์— ํ† ํฐ ์ €์žฅ
    }
    return await axios.get(`${URL}${api}`, {
      headers: { authorization: req.session.jwt },
    }); // API ์š”์ฒญ
  } catch (error) {
    if (error.response?.status === 419) { // ํ† ํฐ ๋งŒ๋ฃŒ ์‹œ ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ๋ฐ›๊ธฐ
      delete req.session.jwt;
      return request(req, api);
    } // 419 ์™ธ์˜ ๋‹ค๋ฅธ ์—๋Ÿฌ์ด๋ฉด
    throw error;
  }
};

//(2)
exports.getMyPosts = async (req, res, next) => {
  try {
    const result = await request(req, '/posts/my');
    res.json(result.data);
  } catch (error) {
    console.error(error);
    next(error);
  }
};

//(3)
exports.searchByHashtag = async (req, res, next) => {
  try {
    const result = await request(
      req,
      `/posts/hashtag/${encodeURIComponent(req.params.hashtag)}`,
    );
    res.json(result.data);
  } catch (error) {
    if (error.code) {
      console.error(error);
      next(error);
    }
  }
};

 

(1) 

- request ํ•จ์ˆ˜: NodeBird API์— ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ํ•จ์ˆ˜

- ํ—ค๋”์˜ origin ๊ฐ’์„ localhost:4000์œผ๋กœ ์„ค์ •: ์–ด๋””์„œ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š”์ง€ ํŒŒ์•…ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•œ๋‹ค.

 

- ์„ธ์…˜์— ํ† ํฐ์ด ์—†์œผ๋ฉด clientSecret์„ ์‚ฌ์šฉํ•ด ํ† ํฐ์„ ๋ฐœ๊ธ‰๋ฐ›๋Š” ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค.

- ๋ฐœ๊ธ‰๋ฐ›์€ ํ›„์—๋Š” ํ† ํฐ์„ ์ด์šฉํ•ด API ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค.

- ํ† ํฐ์€ ์žฌ์‚ฌ์šฉ์„ ์œ„ํ•ด ์„ธ์…˜์— ์ €์žฅํ•œ๋‹ค.

- ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์–ด 419 ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ, ํ† ํฐ์„ ์ง€์šฐ๊ณ  request ํ•จ์ˆ˜๋ฅผ ์žฌ๊ท€์ ์œผ๋กœ ํ˜ธ์ถœ์— ์žฌ์š”์ฒญํ•œ๋‹ค.

 

- ๊ฒฐ๊ด๊ฐ’์˜ ์ฝ”๋“œ์— ๋”ฐ๋ผ ์„ฑ๊ณต ์—ฌ๋ถ€๋ฅผ ์•Œ ์ˆ˜ ์žˆ๊ณ , ์‹คํŒจ ์‹œ์—๋„ ์‹คํŒจ ์ข…๋ฅ˜๋ฅผ ์•Œ ์ˆ˜ ์žˆ๋‹ค.

(2)

- GET  /myposts ๋ผ์šฐํ„ฐ: API๋ฅผ ์‚ฌ์šฉํ•ด ์ž์‹ ์ด ์ž‘์„ฑํ•œ ํฌ์ŠคํŠธ๋ฅผ JSON ํ˜•์‹์œผ๋กœ ๊ฐ€์ ธ์˜ค๋Š” ๋ผ์šฐํ„ฐ

- ํ˜„์žฌ๋Š” JSON์œผ๋กœ ์‘๋‹ตํ•˜์ง€๋งŒ ํ…œํ”Œ๋ฆฟ ์—”์ง„์„ ์‚ฌ์šฉํ•ด ํ™”๋ฉด์„ ๋ Œ๋”๋งํ•  ์ˆ˜ ์žˆ๋‹ค.

 

(3)

- GET/search/:hashtag ๋ผ์šฐํ„ฐ: API๋ฅผ ์‚ฌ์šฉํ•ด ํ•ด์‹œํƒœ๊ทธ๋ฅผ ๊ฒ€์ƒ‰ํ•˜๋Š” ๋ผ์šฐํ„ฐ

 

 

- localhost:4000์— ์ ‘์†ํ•˜๋ฉด GET  / ๋ผ์šฐํ„ฐ๋Š” ๋งŒ๋“ค์ง€ ์•Š์•˜๊ธฐ์— GET  / ๋ผ์šฐํ„ฐ๊ฐ€ ์—†๋‹ค๋Š” ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

- ๋…ธ๋“œ ํ•ด์‹œํƒœ๊ทธ๊ฐ€ ๋‹ฌ๋ฆฐ ๊ฒŒ์‹œ๊ธ€๋“ค์„ ๊ฒ€์ƒ‰ํ•œ ๊ฒฐ๊ณผ์ด๋‹ค.

- ๋ฐ์ดํ„ฐ๊ฐ€ JSON ํ˜•์‹์œผ๋กœ ์˜ค๋ฏ€๋กœ ์›ํ•˜๋Š”๋Œ€๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

 

- 1๋ถ„ ๋’ค ์š”์ฒญ์ด ๋งŒ๋ฃŒ๋˜๊ณ  ๋‚˜์„œ ๋‹ค์‹œ ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด ์•Œ์•„์„œ ํ† ํฐ์„ ์žฌ๋ฐœ๊ธ‰ํ•œ ํ›„ ๋‹ค์‹œ ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค.

- nodebird-api์˜ ์ฝ˜์†”์—๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์„ธ ๊ฐœ์˜ ์š”์ฒญ์ด ๊ธฐ๋ก๋œ๋‹ค.

POST /v1/posts/hashtag/๋…ธ๋“œ 419 0.962 ms - 56
POST /v1/token 200 23.383 ms - 252
POST /v1/posts/hashtag/๋…ธ๋“œ 200 8.288 ms - 395

 

6. ์‚ฌ์šฉ๋Ÿ‰ ์ œํ•œ ๊ตฌํ˜„ํ•˜๊ธฐ

- ์ธ์ฆ๋œ ์‚ฌ์šฉ์ž๋”๋ผ๋„ ๊ณผ๋„ํ•œ API ์‚ฌ์šฉ์€ API ์„œ๋ฒ„์— ๋ฌด๋ฆฌ๊ฐ€ ๊ฐ„๋‹ค.

→ ์ผ์ • ๊ธฐ๊ฐ„ ๋‚ด์— API๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ํšŸ์ˆ˜๋ฅผ ์ œํ•œํ•ด ์„œ๋ฒ„์˜ ํŠธ๋ž˜ํ”ฝ์„ ์ค„์ด๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

- ์ด๋Ÿฌํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•˜๋Š” express-rate-limit ํŒจํ‚ค์ง€๋ฅผ nodebird-api ์„œ๋ฒ„์— ๋‹ค์Œ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•œ๋‹ค.

 

์ฝ˜์†” 

npm i express-rate-limit

 

- verifyToken ๋ฏธ๋“ค์›จ์–ด ์•„๋ž˜์— apiLimiter ๋ฏธ๋“ค์›จ์–ด์™€ deprecated ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

 

nodebird-api/middlewares/index.js

const jwt = require('jsonwebtoken');
const rateLimit = require('express-rate-limit');
...
exports.apiLimiter = rateLimit({
    windowMs: 60 * 1000, // 1๋ถ„
    max: 1,
    handler: function (req, res) {
        res.status(this.statusCode).json({
            code: this.statusCode, // ๊ธฐ๋ณธ๊ฐ’ 429
            message: '1๋ถ„์— ํ•œ ๋ฒˆ๋งŒ ์š”์ฒญํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.'
        });
    }
});

exports.deprecated = (req, res) => {
    res.status(410).json({
        code: 410,
        message: '์ƒˆ๋กœ์šด ๋ฒ„์ „์ด ๋‚˜์™”์Šต๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜์„ธ์š”.'
    });
};

 

- apiLimiter ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋ผ์šฐํ„ฐ์— ๋„ฃ์œผ๋ฉด ๋ผ์šฐํ„ฐ์— ์‚ฌ์šฉ๋Ÿ‰ ์ œํ•œ์ด ๊ฑธ๋ฆฐ๋‹ค.

- 1๋ถ„์— ํ•œ ๋ฒˆ ํ˜ธ์ถœ ๊ฐ€๋Šฅํ•˜๊ณ , ์‚ฌ์šฉ๋Ÿ‰ ์ œํ•œ์„ ์ดˆ๊ณผํ•˜๋ฉด 429 ์ƒํƒœ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ํ—ˆ์šฉ๋Ÿ‰์„ ์ดˆ๊ณผํ–ˆ๋‹ค๋Š” ์‘๋‹ต์„ ์ „์†กํ•œ๋‹ค.

 

- deprecated ๋ฏธ๋“ค์›จ์–ด๋Š” ์‚ฌ์šฉํ•˜๋ฉด ์•ˆ๋˜๋Š” ๋ผ์šฐํ„ฐ์— ๋ถ™์ธ๋‹ค.

- 410 ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์ƒˆ๋กœ์šด ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜๋ผ๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ์‘๋‹ตํ•œ๋‹ค.

 

API ์‘๋‹ต ๋ชฉ๋ก

 

- ์‚ฌ์šฉ๋Ÿ‰ ์ œํ•œ์ด ์ถ”๊ฐ€๋˜์—ˆ์œผ๋ฏ€๋กœ ๊ธฐ์กด API ๋ฒ„์ „๊ณผ ํ˜ธํ™˜๋˜์ง€ ์•Š๋Š”๋‹ค.

- ์ƒˆ๋กœ์šด ๋ผ์šฐํ„ฐ v2๋Š” ๊ธฐ์กด v1๊ณผ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋™์ผํ•˜๋ฏ€๋กœ ์ผ๋ถ€๋งŒ ์ˆ˜์ •๋œ๋‹ค.

 

nodebird-api/routes/v2.js

const express = require('express');
const { verifyToken, apiLimiter} = require('../middlewares');
const { createToken, tokenTest, getMyPosts, get PostsByHashtag}= require(' ../controllers/v2');

const router = express.Router;

// POST /v2/token
router.post('/token', apilimiter, createToken);

// POST /v2/test
router.get('/test', apilimiter, verifyToken, tokenTest);

// GET /v2/posts/my
router.get('/posts/my', apiLimiter, verifyToke n, getMyPosts);

// GET /v2/posts/hashtag/:title
router.get('/posts/hashtag/:title', apiLimiter, verifyToken, getPostsByHashtag);

module.exports=router;

 

nodebird-api/controllers/v2.js

...
exports.createToken = async (req, res) => {
    const { clientSecret } = req.body;
    try {
        const domain = await Domain.findOne({
            where: { clientSecret },
            include: {
                model: User,
                attributes: ['nick', 'id'], // 'attribute'๋ฅผ 'attributes'๋กœ ์ˆ˜์ •
            },
        });
        
        if (!domain) {
            return res.status(401).json({
                code: 401,
                message: '๋“ฑ๋ก๋˜์ง€ ์•Š์€ ๋„๋ฉ”์ธ์ž…๋‹ˆ๋‹ค. ๋จผ์ € ๋„๋ฉ”์ธ์„ ๋“ฑ๋กํ•˜์„ธ์š”',
            });
        }

        const token = jwt.sign({
            id: domain.User.id,
            nick: domain.User.nick,
        }, process.env.JWT_SECRET, {
            expiresIn: '30m', // 30๋ถ„
            issuer: 'nodebird',
        });

        return res.json({
            code: 200,
            message: 'ํ† ํฐ์ด ๋ฐœ๊ธ‰๋˜์—ˆ์Šต๋‹ˆ๋‹ค',
            token,
        });
    } catch (error) {
        console.error(error);
        return res.status(500).json({
            code: 500,
            message: '์„œ๋ฒ„ ์—๋Ÿฌ',
        });
    }
};
...

 

- ํ† ํฐ ์œ ํšจ๊ธฐ๊ฐ„์„ 30๋ถ„์œผ๋กœ ๋Š˜๋ฆฌ๊ณ  ๋ผ์šฐํ„ฐ์— ์‚ฌ์šฉ๋Ÿ‰ ์ œํ•œ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ถ”๊ฐ€ํ–ˆ๋‹ค.

- ๊ธฐ์กด v1 ๋ผ์šฐํ„ฐ๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ๊ฒฝ๊ณ  ๋ฉ”์‹œ์ง€๋ฅผ ๋„์›Œ์ค€๋‹ค.

 

nodebird-api/routes/v1.js

const express = require('express');

const { verifyToken, deprecated } = require('../middlewares');
const { createToken, tokenTest, getMyPosts, getPostsByHashtag } = require('../controllers/v1');

const router = express.Router();

router.use(deprecated);

//POST /v1/token
router.post('/token', createToken);
...

 

- ๋ผ์šฐํ„ฐ ์•ž์— deprecated ๋ฏธ๋“ค์›จ์–ด๋ฅผ ์ถ”๊ฐ€ํ•ด v1์œผ๋กœ ์ ‘๊ทผํ•œ ๋ชจ๋“  ์š”์ฒญ์— deprecated ์‘๋‹ต์„ ๋ณด๋‚ด๋„๋ก ํ•œ๋‹ค.

 

- ์ƒˆ๋กœ ๋งŒ๋“  ๋ผ์šฐํ„ฐ๋ฅผ ์„œ๋ฒ„์™€ ์—ฐ๊ฒฐํ•œ๋‹ค.

nodebird-api/app.js

...
const v1 = require('./routes/v1');
const v2 = require('./routes/v2');
const authRouter = require('./routes/auth');
...
app.use('/v1', v1);
app.use('/v2', v2);
app.use('/auth', authRouter);
...

 

- ์‚ฌ์šฉ์ž ์ž…์žฅ(NodeCat)์œผ๋กœ ๋Œ์•„์™€์„œ ์ƒˆ๋กœ ์ƒ๊ธด ๋ฒ„์ „์„ ํ˜ธ์ถœํ•œ๋‹ค.

nodecat/.env

...
API_URL=http://localhost:8002/v2
...

 

- v2๋กœ ๋ฐ”๊พธ์ง€ ์•Š๊ณ  v1์„ ๊ณ„์† ์‚ฌ์šฉํ•œ๋‹ค๋ฉด 410 ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

- 1๋ถ„์— ํ•œ ๋ฒˆ๋ณด๋‹ค ๋” ๋งŽ์ด API๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด 429 ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

 

7. CORS ์ดํ•ดํ•˜๊ธฐ

- NodeCat์ด nodebird-api๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ๊ฒƒ์€ ์„œ๋ฒ„์—์„œ ์„œ๋ฒ„๋กœ API๋ฅผ ํ˜ธ์ถœํ•œ ๊ฒƒ์ด๋‹ค.

 

โ‘  NodeCat์˜ ํ”„๋ŸฐํŠธ์—์„œ nodebird-api์˜ ์„œ๋ฒ„ API๋ฅผ ํ˜ธ์ถœ๋œ๋‹ค๋ฉด?

 

nodecat/routes/index.js

const express = require('express');
const { searchByHashtag, getMyPosts, renderMain } = require('../controllers');

const router = express.Router();

router.get('/myposts', getMyPosts);
router.get('/search/:hashtag', searchByHashtag);
router.get('/', renderMain);

module.exports = router;

 

nodecat/controllers/index.js

..
exports.renderMain = (req, res) => {
	res.render('main', { key: process.env.CLIENT_SECRET });
};

 

โ‘ก ํ”„๋ŸฐํŠธ ํ™”๋ฉด๋„ ์ถ”๊ฐ€ํ•œ๋‹ค.

nodecat/views/main.html

<!DOCTYPE html>
<html>
	<head>
		<title>ํ”„๋ŸฐํŠธ API ์š”์ฒญ</title>
	</head>
	<body>
	<div id="result"></div>
	<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
	<script>
		axios.post('http://localhost:8002/v2/token', {clientSecret: '{{key}}',})
        //{{key}}๋ถ€๋ถ„์ด ๋„Œ์ ์Šค์— ์˜ํ•ด ์‹ค์ œ ํ‚ค๋กœ ์น˜ํ™˜๋˜์–ด ๋ Œ๋”๋ง ๋œ๋‹ค.
	.then((res) => {
		document.querySelector('#result').textContent = JSON.stringify(res.data);
	})
	.catch((err) => {
		console.error(err);
	});
	</script>
	</body>
</html>

 - ์‹ค์ œ ์„œ๋น„์Šค์—์„œ๋Š” ์„œ๋ฒ„์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋น„๋ฐ€ ํ‚ค์™€ ํ”„๋ŸฐํŠธ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋น„๋ฐ€ ํ‚ค๋ฅผ ๋”ฐ๋กœ ๋‘๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

→ clientSecret ์™ธ์— frontSecret ๊ฐ™์€ ์ปฌ๋Ÿผ์„ ์ถ”๊ฐ€ํ•ด ๋”ฐ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์„ ๊ถŒ์žฅํ•œ๋‹ค.

 

โ‘ข CORS ์—๋Ÿฌ

- https://localhost:4000์— ์ ‘์†ํ•˜๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉฐ ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜์ง€ ์•Š๋Š”๋‹ค.

Access-Control-Allow-Origin ์—๋Ÿฌ

- Access-Control-Allow-Origin ์—๋Ÿฌ: ํ—ค๋”๊ฐ€ ์—†๋‹ค๋Š” ๋‚ด์šฉ์˜ ์—๋Ÿฌ

- ๋ธŒ๋ผ์šฐ์ €์™€ ์„œ๋ฒ„์˜ ๋„๋ฉ”์ธ์ด ์ผ์น˜ํ•˜์ง€ ์•Š์œผ๋ฉด ๊ธฐ๋ณธ์ ์œผ๋กœ ์š”์ฒญ์ด ์ฐจ๋‹จ๋œ๋‹ค.

CORS ์—๋Ÿฌ

- CORS ๋ฌธ์ œ: ํ˜„์žฌ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ํด๋ผ์ด์–ธํŠธ(localhost:4000)์™€ ์š”์ฒญ์„ ๋ฐ›๋Š” ์„œ๋ฒ„(localhost:8002)์˜ ๋„๋ฉ”์ธ์ด ๋‹ฌ๋ผ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ

- Network ํƒญ์—๋Š” ๋ณด๋‚ธ ์  ์—†๋Š” OPTIONS ๋ฉ”์„œ๋“œ๊ฐ€ ํ‘œ์‹œ๋˜๊ณ , POST ์š”์ฒญ์€ CORS error ์‹คํŒจํ–ˆ๋‹ค๊ณ  ๋œฌ๋‹ค.

- OPTIONS ๋ฉ”์„œ๋“œ๋Š” ์‹ค์ œ ์š”์ฒญ์„ ๋ณด๋‚ด๊ธฐ ์ „์— ์„œ๋ฒ„๊ฐ€ ์š”์ฒญ์˜ ๋„๋ฉ”์ธ, ํ—ค๋”์™€ ๋ฉ”์„œ๋“œ ๋“ฑ์„ ํ—ˆ์šฉํ•˜๋Š”์ง€ ์ฒดํฌํ•˜๋Š” ์—ญํ• ์„ ํ•œ

๋‹ค.

 

NodeBird API ์ฝ˜์†”

OPTIONS /v2/token 200 0.302 ms - 4

- NodeBird API ์„œ๋ฒ„ ์ฝ˜์†”์—๋„ OPTIONS ์š”์ฒญ์ด ๊ธฐ๋ก๋œ๋‹ค.

 

โ‘ฃ CORS ๋ฌธ์ œ ํ•ด๊ฒฐํ•˜๊ธฐ

- ์‘๋‹ต ํ—ค๋”์— ํด๋ผ์ด์–ธํŠธ ๋„๋ฉ”์ธ์˜ ์š”์ฒญ์„ ํ—ˆ๋ฝํ•œ๋‹ค๋Š” ๋œป์ธ Access-Control-Allow-Origin ํ—ค๋”๋ฅผ ๋„ฃ์–ด์•ผํ•œ๋‹ค.

- ์‘๋‹ต ํ—ค๋”๋ฅผ ์กฐ์ž‘ํ•˜๋ ค๋ฉด ์‘๋‹ต์€ API ์„œ๋ฒ„๊ฐ€ ๋ณด๋‚ด๋Š” ๊ฒƒ์ด๊ธฐ์— NodeBird API ์„œ๋ฒ„์—์„œ ๋ฐ”๊ฟ”์•ผํ•œ๋‹ค.

→ NodeBird API์— cors ๋ชจ๋“ˆ์„ ์„ค์น˜ํ•˜๋ฉด ๋œ๋‹ค.

$ npm i cors

- cors ํŒจํ‚ค์ง€ ์„ค์น˜ ํ›„ v2.js์— ์ ์šฉํ•œ๋‹ค.

 

nodebird-api/routes/v2.js

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

const { verifyToken, apiLimiter } = require('../middlewares');
const { createToken, tokenTest, getMyPosts, getPostsByHashtag } = require('../controllers/v2');

const router = express.Router();
router.use(cors({
	credentials: true, //๋‹ค๋ฅธ ๋„๋ฉ”์ธ ๊ฐ„์— ์ฟ ํ‚ค๊ฐ€ ๊ณต์œ ๋œ๋‹ค. 
}));
...

- ์ด์ œ ์‘๋‹ต์— Access-Control-Allow-Origin ํ—ค๋”๊ฐ€ ์ถ”๊ฐ€๋˜์–ด ๋‚˜๊ฐ„๋‹ค.

- credentials: true๋ฅผ ํ™œ์„ฑํ™”ํ•ด์•ผ ๋‹ค๋ฅธ ๋„๋ฉ”์ธ ๊ฐ„์— ์ฟ ํ‚ค๊ฐ€ ๊ณต์œ ๋œ๋‹ค. 

- credentials: true๋ฅผ ํ™œ์„ฑํ™”ํ•˜์ง€ ์•Š์œผ๋ฉด ํ”„๋ŸฐํŠธ์™€ ์„œ๋ฒ„์˜ ๋„๋ฉ”์ธ์ด ๋‹ค๋ฅธ ๊ฒฝ์šฐ ๋กœ๊ทธ์ธ์ด ๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ๋‹ค.

CORS ์š”์ฒญ ์„ฑ๊ณต

- https://localhost:4000์— ์ ‘์†ํ•˜๋ฉด ํ† ํฐ์ด ๋ฐœ๊ธ‰๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.

- ์ด ํ† ํฐ์„ ์‚ฌ์šฉํ•ด ๋‹ค๋ฅธ API ์š”์ฒญ์„ ๋ณด๋‚ด๋ฉด ๋œ๋‹ค.

Access-Control-Allow-Origin: *

- Access-Control-Allow-Origin:*์€ ๋ชจ๋“  ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์„ ํ—ˆ์šฉํ•œ๋‹ค๋Š” ๋œป์ด๋‹ค.

- credentials:true ์˜ต์…˜์€ Access-Control-Allow-Credentials ํ—ค๋”๋ฅผ true๋กœ ๋งŒ๋“ ๋‹ค.

 

→ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ์ฃผ์ฒด๊ฐ€ ํด๋ผ์ด์–ธํŠธ๋ผ์„œ ๋น„๋ฐ€ํ‚ค๊ฐ€ ๋ชจ๋‘์—๊ฒŒ ๋…ธ์ถœ๋˜๋Š” ์ƒˆ๋กœ์šด ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

CORS ์š”์ฒญ๋„ ํ—ˆ์šฉํ–ˆ์œผ๋ฏ€๋กœ ์ด ๋น„๋ฐ€ ํ‚ค๋ฅผ ๊ฐ€์ง€๊ณ  ๋‹ค๋ฅธ ๋„๋ฉ”์ธ๋“ค์ด API ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ผ ์ˆ˜ ์žˆ๋‹ค.

 

→ ๋น„๋ฐ€ ํ‚ค ๋ฐœ๊ธ‰ ์‹œ ํ—ˆ์šฉํ•œ ๋„๋ฉ”์ธ์„ ์ ๊ฒŒ ํ–ˆ์œผ๋‹ˆ ํ˜ธ์ŠคํŠธ์™€ ๋น„๋ฐ€ ํ‚ค๊ฐ€ ๋ชจ๋‘ ์ผ์น˜ํ•  ๋•Œ๋งŒ CORS๋ฅผ ํ—ˆ์šฉํ•˜๊ฒŒ ์ˆ˜์ •ํ•œ๋‹ค.

nodebird-api/routes/v2.js

const express = require('express');

const { verifyToken, apiLimiter, corsWhenDomainMatches } = require('../middlewares');
const { createToken, tokenTest, getMyPosts, getPostsByHashtag } = require('../controllers/v2');

const router = express.Router();

router.use(corsWhenDomainMatches);

// POST /v2/token
router.post('/token', apiLimiter, createToken);
...

nodebird-api/middlewares/index.js

const jwt = require('jsonwebtoken');
const rateLimit = require('express-rate-limit');
const cors = require('cors');
const { Domain } = require('../models');
...
exports.corsWhenDomainMatches = async (req, res, next) => {
	const domain = await Domain.findOne({
		where: { host: new URL(req.get('origin')).host },
	});
	if (domain) {
		cors({ 
			origin: req.get('origin'),
			credentials: true,
		})(req, res, next);
	} else {
		next();
	}
};

- ํด๋ผ์ด์–ธํŠธ์˜ ๋„๋ฉ”์ธ(req,get('origin'))๊ณผ ํ˜ธ์ŠคํŠธ๊ฐ€ ์ผ์น˜ํ•˜๋Š” ๊ฒƒ์ด ์žˆ๋Š”์ง€ ๊ฒ€์‚ฌํ•œ๋‹ค.

- ์ผ์น˜ํ•œ๋‹ค๋ฉด CORS ํ—ˆ์šฉํ•ด ๋‹ค์Œ ๋ฏธ๋“ค์›จ์–ด๋กœ ๋ณด๋‚ด๊ณ , ์ผ์น˜ํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด CORS ์—†์ด next๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.

- cors ๋ฏธ๋“ค์›จ์–ด์˜ ์ธ์ˆ˜์— origin ์†์„ฑ์„ ์ถ”๊ฐ€ํ•ด ํ—ˆ์šฉํ•  ๋„๋ฉ”์ธ๋งŒ ๋”ฐ๋กœ ์ ๋Š”๋‹ค.

 

- ๋‹ค์‹œ http://localhost:4000์— ์ ‘์†ํ•˜๋ฉด ์„ฑ๊ณต์ ์œผ๋กœ ํ† ํฐ์„ ๊ฐ€์ ธ์˜จ๋‹ค.

ํŠน์ • ๋„๋ฉ”์ธ๋งŒ CORS ํ—ˆ์šฉ

- Access-Control-Allow-Origin์ด * ๋Œ€์‹  http://localhost:4000์œผ๋กœ ์ ์šฉ๋˜์–ด ์žˆ๋‹ค.

- ํŠน์ • ๋„๋ฉ”์ธ๋งŒ ํ—ˆ์šฉํ•˜๋ฏ€๋กœ ํ—ˆ์šฉ๋˜์ง€ ์•Š์€ ๋‹ค๋ฅธ ๋„๋ฉ”์ธ์—์„œ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ๊ฒƒ์„ ์ฐจ๋‹จํ•  ์ˆ˜ ์žˆ๋‹ค.

 

 


quiz

1. ๋‹ค๋ฅธ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ ํ˜„์žฌ ํ”„๋กœ๊ทธ๋žจ์˜ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ—ˆ์šฉํ•˜๋Š” ์ ‘์ ์„ (   )๋ผ๊ณ  ํ•œ๋‹ค.

2. JWT๋Š” (     ) ํ˜•์‹์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๋Š” ํ† ํฐ์ด๋‹ค.

3. JWT ํ† ํฐ์€(    )๋ฅผ ์•Œ์ง€ ์•Š๋Š” ์ด์ƒ ๋ณ€์กฐ๊ฐ€ ๋ถˆ๊ฐ€๋Šฅํ•˜๊ณ , ๋ณ€์กฐํ•œ ํ† ํฐ์€ (     )๋ฅผ ํ†ตํ•ด ๊ฒ€์‚ฌํ•  ๋•Œ ๊ตฌ๋ณ„ํ•  ์ˆ˜ ์žˆ๋‹ค. 

4.  API ์‚ฌ์šฉํ•˜๋Š” ์„œ๋น„์Šค๋Š” ๋‹ค๋ฅธ ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ด๋ฏ€๋กœ (      ) ์—ญํ• ์„ ํ•œ๋‹ค.

5. (        )๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋ผ์šฐํ„ฐ์— ๋„ฃ์œผ๋ฉด ๋ผ์šฐํ„ฐ์— ์‚ฌ์šฉ๋Ÿ‰ ์ œํ•œ์ด ๊ฑธ๋ฆฐ๋‹ค.

6. ํ˜„์žฌ ์š”์ฒญ์„ ๋ณด๋‚ด๋Š” ํด๋ผ์ด์–ธํŠธ์™€ ์š”์ฒญ์„ ๋ฐ›๋Š” ์„œ๋ฒ„์˜ ๋„๋ฉ”์ธ์ด ๋‹ฌ๋ผ ๋ฐœ์ƒํ•˜๋Š” ๋ฌธ์ œ๋ฅผ (      )๋ฌธ์ œ๋ผ๊ณ  ํ•œ๋‹ค.

7. (      ) ์˜ต์…˜์€ Access-Control-Allow-Credentials ํ—ค๋”๋ฅผ true๋กœ ๋งŒ๋“ ๋‹ค.

 

programming quiz

8. ๋‹ค์Œ ์ฃผ์„์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ฝ”๋“œ์˜ ๋นˆ์นธ์„ ์ฑ„์šฐ์‹œ์˜ค.

const express = require('express');

const { verifyToken } = require('../middlewares');
const { createToken, tokenTest } = require('../controllers/v1');

const router = express.Router();

// POST /v1/token: ํ† ํฐ์„ ๋ฐœ๊ธ‰ํ•˜๋Š” ๋ผ์šฐํ„ฐ
router.______(________, _________);

// GET /v1/test: ์‚ฌ์šฉ์ž๊ฐ€ ํ† ํฐ์„ ํ…Œ์ŠคํŠธํ•ด๋ณผ ์ˆ˜ ์žˆ๋Š” ๋ผ์šฐํ„ฐ
router.______(________, _________);

module.exports = router;

 

9. ๋‹ค์Œ ์ฃผ์„์„ ์ฐธ๊ณ ํ•˜์—ฌ ์ฝ”๋“œ์˜ ๋นˆ์นธ์„ ์ฑ„์šฐ์‹œ์˜ค.

const jwt = require('jsonwebtoken');
const rateLimit = require('express-rate-limit');
...
exports.apiLimiter = rateLimit({
    windowMs: 60 * 1000, // 1๋ถ„
    max: 1,
    handler: function (req, res) {
        res.status(_________).json({
            code: __________, // ๊ธฐ๋ณธ๊ฐ’ 429
            message: '1๋ถ„์— ํ•œ ๋ฒˆ๋งŒ ์š”์ฒญํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.'
        });
    }
});

exports.deprecated = (req, res) => {
    res.status(______).json({ //์—๋Ÿฌ์ฝ”๋“œ 401
        code: ______,
        message: '์ƒˆ๋กœ์šด ๋ฒ„์ „์ด ๋‚˜์™”์Šต๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜์„ธ์š”.'
    });
};

 

 


Answer

1. API

2. JSON

3. JWT ๋น„๋ฐ€ ํ‚ค, ์‹œ๊ทธ๋‹ˆ์ฒ˜

4. ํด๋ผ์ด์–ธํŠธ

5. apiLimiter

6. CORS

7. credential:true

8.

const express = require('express');

const { verifyToken } = require('../middlewares');
const { createToken, tokenTest } = require('../controllers/v1');

const router = express.Router();

// POST /v1/token: ํ† ํฐ์„ ๋ฐœ๊ธ‰ํ•˜๋Š” ๋ผ์šฐํ„ฐ
router.post('/token', createToken);

// GET /v1/test: ์‚ฌ์šฉ์ž๊ฐ€ ํ† ํฐ์„ ํ…Œ์ŠคํŠธํ•ด๋ณผ ์ˆ˜ ์žˆ๋Š” ๋ผ์šฐํ„ฐ
router.get('/test', verifyToken, tokenTest);

module.exports = router;

9.

const jwt = require('jsonwebtoken');
const rateLimit = require('express-rate-limit');
...
exports.apiLimiter = rateLimit({
    windowMs: 60 * 1000, // 1๋ถ„
    max: 1,
    handler: function (req, res) {
        res.status(this.statusCode).json({
            code: this.statusCode, // ๊ธฐ๋ณธ๊ฐ’ 429
            message: '1๋ถ„์— ํ•œ ๋ฒˆ๋งŒ ์š”์ฒญํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.'
        });
    }
});

exports.deprecated = (req, res) => {
    res.status(410).json({
        code: 410,
        message: '์ƒˆ๋กœ์šด ๋ฒ„์ „์ด ๋‚˜์™”์Šต๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด ๋ฒ„์ „์„ ์‚ฌ์šฉํ•˜์„ธ์š”.'
    });
};

 

 

 

์ถœ์ฒ˜) ์กฐํ˜„์ •, ใ€ŒNode.js ๊ต๊ณผ์„œ ๊ฐœ์ • 3ํŒใ€, ๊ธธ๋ฒ—(2022), 10์žฅ

 

Corner node.js 2ํŒ€

Editor : Sullivan

 

 

728x90

๊ด€๋ จ๊ธ€ ๋”๋ณด๊ธฐ