๐Ÿš€ NestJS ํ”„๋ ˆ์ž„์›Œํฌ ๋งˆ์Šคํ„ฐํ•˜๊ธฐ: ์ดˆ๋ณด์ž๋ถ€ํ„ฐ ์ „๋ฌธ๊ฐ€๊นŒ์ง€! ๐Ÿš€

์ฝ˜ํ…์ธ  ๋Œ€ํ‘œ ์ด๋ฏธ์ง€ - ๐Ÿš€ NestJS ํ”„๋ ˆ์ž„์›Œํฌ ๋งˆ์Šคํ„ฐํ•˜๊ธฐ: ์ดˆ๋ณด์ž๋ถ€ํ„ฐ ์ „๋ฌธ๊ฐ€๊นŒ์ง€! ๐Ÿš€

 

 

์•ˆ๋…•ํ•˜์„ธ์š”, ์—ด์ • ๋„˜์น˜๋Š” ๊ฐœ๋ฐœ์ž ์—ฌ๋Ÿฌ๋ถ„! ์˜ค๋Š˜์€ ํ˜„๋Œ€ ์›น ๊ฐœ๋ฐœ์˜ ํ•ซํ•œ ํŠธ๋ Œ๋“œ, NestJS ํ”„๋ ˆ์ž„์›Œํฌ์— ๋Œ€ํ•ด ๊นŠ์ด ์žˆ๊ฒŒ ํƒ๊ตฌํ•ด๋ณด๋ ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ๐ŸŽ‰ NestJS๋Š” ํšจ์œจ์ ์ด๊ณ  ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌ์ถ•ํ•˜๋Š” ๋ฐ ํƒ์›”ํ•œ ๋„๊ตฌ์ธ๋ฐ์š”, ์ด ๊ธ€์„ ํ†ตํ•ด ์—ฌ๋Ÿฌ๋ถ„๋„ NestJS์˜ ๋งˆ๋ฒ• ๊ฐ™์€ ์„ธ๊ณ„๋กœ ๋น ์ ธ๋“ค๊ฒŒ ๋  ๊ฑฐ์˜ˆ์š”!

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

๐Ÿ’ก Pro Tip: ์ด ๊ธ€์—์„œ ๋ฐฐ์šด ๋‚ด์šฉ์„ ์‹ค์ œ ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•ด๋ณด์„ธ์š”. ์‹ค์ „ ๊ฒฝํ—˜๋งŒํผ ์ข‹์€ ํ•™์Šต ๋ฐฉ๋ฒ•์€ ์—†๋‹ต๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ๋ถ„์˜ ์ฐฝ์˜์ ์ธ ์•„์ด๋””์–ด๋ฅผ NestJS๋กœ ๊ตฌํ˜„ํ•ด๋ณด๋Š” ๊ฑด ์–ด๋–จ๊นŒ์š”? ์˜ˆ๋ฅผ ๋“ค์–ด, ์žฌ๋Šฅ๋„ท๊ณผ ๊ฐ™์€ ์žฌ๋Šฅ ๊ณต์œ  ํ”Œ๋žซํผ์˜ ๋ฐฑ์—”๋“œ๋ฅผ NestJS๋กœ ๊ตฌ์ถ•ํ•ด๋ณด๋Š” ๊ฒƒ๋„ ์ข‹์€ ์—ฐ์Šต์ด ๋  ๊ฑฐ์˜ˆ์š”!

์ž, ๊ทธ๋Ÿผ NestJS์˜ ์‹ ๋น„๋กœ์šด ์„ธ๊ณ„๋กœ ํ•จ๊ป˜ ๋– ๋‚˜๋ณผ๊นŒ์š”? ๐Ÿš€

1. NestJS ์†Œ๊ฐœ: ์›น ๊ฐœ๋ฐœ์˜ ์ƒˆ๋กœ์šด ์ง€ํ‰ ๐ŸŒ…

NestJS๋Š” ํšจ์œจ์ ์ด๊ณ  ํ™•์žฅ ๊ฐ€๋Šฅํ•œ Node.js ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌ์ถ•ํ•˜๊ธฐ ์œ„ํ•œ ํ”„๋ ˆ์ž„์›Œํฌ์ž…๋‹ˆ๋‹ค. Angular์—์„œ ์˜๊ฐ์„ ๋ฐ›์•„ ์„ค๊ณ„๋˜์—ˆ์œผ๋ฉฐ, TypeScript๋ฅผ ์™„๋ฒฝํ•˜๊ฒŒ ์ง€์›ํ•ฉ๋‹ˆ๋‹ค. ๐Ÿฆธโ€โ™‚๏ธ

1.1 NestJS์˜ ํŠน์ง•

  • ๐Ÿ“ ๊ตฌ์กฐํ™”๋œ ์•„ํ‚คํ…์ฒ˜: ๋ชจ๋“ˆ, ์ปจํŠธ๋กค๋Ÿฌ, ์„œ๋น„์Šค ๋“ฑ์˜ ๋ช…ํ™•ํ•œ ๊ตฌ์กฐ
  • ๐Ÿ”ง ์˜์กด์„ฑ ์ฃผ์ž…: ๋Š์Šจํ•œ ๊ฒฐํ•ฉ๊ณผ ํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ ์ œ๊ณต
  • ๐Ÿš€ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ๊ธฐ๋ฐ˜: ๊ฐ„๊ฒฐํ•˜๊ณ  ์ง๊ด€์ ์ธ ์ฝ”๋“œ ์ž‘์„ฑ ๊ฐ€๋Šฅ
  • ๐Ÿ”Œ ๋‹ค์–‘ํ•œ ์–ด๋Œ‘ํ„ฐ: Express, Fastify ๋“ฑ ๋‹ค์–‘ํ•œ HTTP ํ”Œ๋žซํผ ์ง€์›
  • ๐Ÿ›  ํ’๋ถ€ํ•œ ์ƒํƒœ๊ณ„: ๋‹ค์–‘ํ•œ ๋‚ด์žฅ ๋ฐ ์„œ๋“œํŒŒํ‹ฐ ๋ชจ๋“ˆ ์ œ๊ณต

๐ŸŽ“ ํ•™์Šต ํฌ์ธํŠธ: NestJS์˜ ๊ตฌ์กฐํ™”๋œ ์•„ํ‚คํ…์ฒ˜๋Š” ๋Œ€๊ทœ๋ชจ ํ”„๋กœ์ ํŠธ์—์„œ ํŠนํžˆ ๋น›์„ ๋ฐœํ•ฉ๋‹ˆ๋‹ค. ์ฝ”๋“œ์˜ ์žฌ์‚ฌ์šฉ์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์ด ํฌ๊ฒŒ ํ–ฅ์ƒ๋˜์ฃ . ์ด๋Š” ์žฌ๋Šฅ๋„ท๊ณผ ๊ฐ™์€ ๋ณต์žกํ•œ ํ”Œ๋žซํผ์„ ๊ฐœ๋ฐœํ•  ๋•Œ ํฐ ์žฅ์ ์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

1.2 NestJS vs Express

Express๋Š” ์˜ค๋žซ๋™์•ˆ Node.js ์ƒํƒœ๊ณ„์˜ ๋Œ€ํ‘œ์ฃผ์ž์˜€์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ NestJS๋Š” Express์˜ ์žฅ์ ์„ ํก์ˆ˜ํ•˜๋ฉด์„œ๋„ ๋” ๋‚˜์€ ๊ตฌ์กฐ์™€ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

ํŠน์ง• Express NestJS
์•„ํ‚คํ…์ฒ˜ ์ž์œ ๋กœ์šด ๊ตฌ์กฐ ๋ชจ๋“ˆํ™”๋œ ๊ตฌ์กฐ
TypeScript ์ง€์› ๋ถ€๋ถ„์  ์ง€์› ์™„๋ฒฝํ•œ ์ง€์›
์˜์กด์„ฑ ์ฃผ์ž… ์—†์Œ ๋‚ด์žฅ
ํ•™์Šต ๊ณก์„  ๋‚ฎ์Œ ์ค‘๊ฐ„

NestJS๋Š” Express์˜ ๊ฐ„๊ฒฐํ•จ๊ณผ ์œ ์—ฐ์„ฑ์„ ์œ ์ง€ํ•˜๋ฉด์„œ๋„, ๋Œ€๊ทœ๋ชจ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ์— ํ•„์š”ํ•œ ๊ตฌ์กฐ์™€ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์ด๋Š” ํ”„๋กœ์ ํŠธ์˜ ํ™•์žฅ์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.

1.3 NestJS์˜ ์ฒ ํ•™

NestJS๋Š” "๊ฐœ๋ฐœ์ž ๊ฒฝํ—˜"์„ ์ค‘์š”ํ•˜๊ฒŒ ์—ฌ๊น๋‹ˆ๋‹ค. ์ด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฒ ํ•™์œผ๋กœ ๋‚˜ํƒ€๋‚ฉ๋‹ˆ๋‹ค:

  • ๐Ÿ— ๊ตฌ์กฐํ™”๋œ ์ฝ”๋“œ๋ฒ ์ด์Šค: ์ผ๊ด€๋œ ์ฝ”๋“œ ๊ตฌ์กฐ๋กœ ํŒ€ ํ˜‘์—… ์šฉ์ด
  • ๐Ÿ” ๋ช…ํ™•ํ•œ ์ถ”์ƒํ™”: ๋ณต์žกํ•œ ๋กœ์ง์„ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ํ‘œํ˜„
  • ๐Ÿ”„ ๋ชจ๋“ˆ์„ฑ: ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ์ปดํฌ๋„ŒํŠธ๋กœ ๊ฐœ๋ฐœ ํšจ์œจ์„ฑ ์ฆ๋Œ€
  • ๐Ÿ›  ํ™•์žฅ์„ฑ: ์‰ฌ์šด ๊ธฐ๋Šฅ ์ถ”๊ฐ€์™€ ์ˆ˜์ •

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

1.4 NestJS ์‹œ์ž‘ํ•˜๊ธฐ

NestJS๋ฅผ ์‹œ์ž‘ํ•˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ ๋ช…๋ น์–ด๋กœ NestJS CLI๋ฅผ ์„ค์น˜ํ•˜๊ณ  ์ƒˆ ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:


npm i -g @nestjs/cli
nest new project-name
  

์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๊ธฐ๋ณธ ๊ตฌ์กฐ์˜ NestJS ํ”„๋กœ์ ํŠธ๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ๐ŸŽŠ

NestJS ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ NestJS ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ src/ test/ node_modules/ main.ts app.module.ts app.controller.ts app.service.ts

์ด ๊ตฌ์กฐ๋ฅผ ์ดํ•ดํ•˜๋Š” ๊ฒƒ์ด NestJS ๋งˆ์Šคํ„ฐ์˜ ์ฒซ ๊ฑธ์Œ์ž…๋‹ˆ๋‹ค. ๊ฐ ํŒŒ์ผ์˜ ์—ญํ• ์„ ์•Œ์•„๋ณผ๊นŒ์š”?

  • ๐Ÿ“ src/: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์†Œ์Šค ์ฝ”๋“œ๊ฐ€ ์œ„์น˜
  • ๐Ÿ“ test/: ํ…Œ์ŠคํŠธ ํŒŒ์ผ๋“ค์ด ์œ„์น˜
  • ๐Ÿ“ node_modules/: ํ”„๋กœ์ ํŠธ ์˜์กด์„ฑ ๋ชจ๋“ˆ๋“ค์ด ์„ค์น˜๋˜๋Š” ๊ณณ
  • ๐Ÿ“„ main.ts: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์—”ํŠธ๋ฆฌ ํฌ์ธํŠธ
  • ๐Ÿ“„ app.module.ts: ๋ฃจํŠธ ๋ชจ๋“ˆ ์ •์˜
  • ๐Ÿ“„ app.controller.ts: ๊ธฐ๋ณธ ์ปจํŠธ๋กค๋Ÿฌ
  • ๐Ÿ“„ app.service.ts: ๊ธฐ๋ณธ ์„œ๋น„์Šค

๐ŸŒŸ ์„ฑ์žฅ ํฌ์ธํŠธ: NestJS์˜ ๊ตฌ์กฐ๋ฅผ ์ดํ•ดํ•˜๊ณ  ๋‚˜๋ฉด, ๋ณต์žกํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๋„ ์ฒด๊ณ„์ ์œผ๋กœ ๊ตฌ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์žฌ๋Šฅ๋„ท๊ณผ ๊ฐ™์€ ๋Œ€๊ทœ๋ชจ ํ”Œ๋žซํผ์„ ๊ฐœ๋ฐœํ•  ๋•Œ ํŠนํžˆ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ๊ธฐ๋Šฅ์„ ๋ชจ๋“ˆ๋กœ ๋‚˜๋ˆ„๊ณ , ๊ด€๋ จ ์ปจํŠธ๋กค๋Ÿฌ์™€ ์„œ๋น„์Šค๋ฅผ ๊ตฌ์„ฑํ•˜๋ฉด ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์ด ํฌ๊ฒŒ ํ–ฅ์ƒ๋ฉ๋‹ˆ๋‹ค.

์ด์ œ NestJS์˜ ๊ธฐ๋ณธ์„ ์•Œ์•˜์œผ๋‹ˆ, ๋” ๊นŠ์ด ๋“ค์–ด๊ฐ€ ๋ณผ๊นŒ์š”? ๋‹ค์Œ ์„น์…˜์—์„œ๋Š” NestJS์˜ ํ•ต์‹ฌ ๊ฐœ๋…๋“ค์„ ์ž์„ธํžˆ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ค€๋น„๋˜์…จ๋‚˜์š”? Let's dive in! ๐ŸŠโ€โ™‚๏ธ

2. NestJS์˜ ํ•ต์‹ฌ ๊ฐœ๋…: ๋ชจ๋“ˆ, ์ปจํŠธ๋กค๋Ÿฌ, ์„œ๋น„์Šค ๐Ÿงฉ

NestJS์˜ ์•„๋ฆ„๋‹ค์›€์€ ๊ทธ ๊ตฌ์กฐ์  ์šฐ์•„ํ•จ์— ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์„น์…˜์—์„œ๋Š” NestJS์˜ ํ•ต์‹ฌ ๊ตฌ์„ฑ ์š”์†Œ์ธ ๋ชจ๋“ˆ, ์ปจํŠธ๋กค๋Ÿฌ, ์„œ๋น„์Šค์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด ๊ฐœ๋…๋“ค์„ ์ œ๋Œ€๋กœ ์ดํ•ดํ•˜๋ฉด, ๋ณต์žกํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๋„ ์ฒด๊ณ„์ ์œผ๋กœ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ๋‹ต๋‹ˆ๋‹ค! ๐Ÿ—๏ธ

2.1 ๋ชจ๋“ˆ (Modules)

๋ชจ๋“ˆ์€ NestJS ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ธฐ๋ณธ ๋นŒ๋”ฉ ๋ธ”๋ก์ž…๋‹ˆ๋‹ค. ๊ด€๋ จ๋œ ๊ธฐ๋Šฅ๋“ค์„ ๊ทธ๋ฃนํ™”ํ•˜๊ณ  ๊ตฌ์กฐํ™”ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

๐ŸŽ“ ํ•ต์‹ฌ ๊ฐœ๋…: ๋ชจ๋“ˆ์€ @Module() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋กœ ์ฃผ์„์ด ๋‹ฌ๋ฆฐ ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค. ์ด ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” Nest๊ฐ€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ตฌ์กฐ๋ฅผ ๊ตฌ์„ฑํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•˜๋Š” ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

๋ชจ๋“ˆ์˜ ๊ธฐ๋ณธ ๊ตฌ์กฐ๋ฅผ ์‚ดํŽด๋ณผ๊นŒ์š”?


import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
import { UserService } from './user.service';

@Module({
  imports: [],
  controllers: [UserController],
  providers: [UserService],
  exports: [UserService],
})
export class UserModule {}
  

์—ฌ๊ธฐ์„œ ๊ฐ ์†์„ฑ์˜ ์˜๋ฏธ๋ฅผ ์•Œ์•„๋ด…์‹œ๋‹ค:

  • ๐Ÿ“ฆ imports: ์ด ๋ชจ๋“ˆ์—์„œ ์‚ฌ์šฉํ•  ๋‹ค๋ฅธ ๋ชจ๋“ˆ๋“ค์„ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
  • ๐ŸŽฎ controllers: ์ด ๋ชจ๋“ˆ์— ์†ํ•œ ์ปจํŠธ๋กค๋Ÿฌ๋“ค์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
  • ๐Ÿ›  providers: ์ด ๋ชจ๋“ˆ์—์„œ ์‚ฌ์šฉํ•  ์„œ๋น„์Šค๋‚˜ ๊ธฐํƒ€ ํ”„๋กœ๋ฐ”์ด๋”๋“ค์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.
  • ๐Ÿ“ค exports: ์ด ๋ชจ๋“ˆ์—์„œ ๋‹ค๋ฅธ ๋ชจ๋“ˆ๋กœ ๋‚ด๋ณด๋‚ผ ํ”„๋กœ๋ฐ”์ด๋”๋“ค์„ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

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

2.2 ์ปจํŠธ๋กค๋Ÿฌ (Controllers)

์ปจํŠธ๋กค๋Ÿฌ๋Š” ๋“ค์–ด์˜ค๋Š” ์š”์ฒญ์„ ์ฒ˜๋ฆฌํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ์— ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. ๋ผ์šฐํŒ… ๋ฉ”์ปค๋‹ˆ์ฆ˜์„ ํ†ตํ•ด ๊ฐ ์ปจํŠธ๋กค๋Ÿฌ๊ฐ€ ํŠน์ • ์š”์ฒญ์„ ์ˆ˜์‹ ํ•˜๋„๋ก ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค.

NestJS ์ปจํŠธ๋กค๋Ÿฌ ์ž‘๋™ ๋ฐฉ์‹ NestJS ์ปจํŠธ๋กค๋Ÿฌ ์ž‘๋™ ๋ฐฉ์‹ ํด๋ผ์ด์–ธํŠธ ์„œ๋น„์Šค ์ปจํŠธ๋กค๋Ÿฌ ์š”์ฒญ ๋ฐ์ดํ„ฐ ์š”์ฒญ ๋ฐ์ดํ„ฐ ์‘๋‹ต ์‘๋‹ต

์ปจํŠธ๋กค๋Ÿฌ์˜ ๊ธฐ๋ณธ ๊ตฌ์กฐ๋ฅผ ์‚ดํŽด๋ณผ๊นŒ์š”?


import { Controller, Get, Post, Body } from '@nestjs/common';
import { UserService } from './user.service';

@Controller('users')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Get()
  findAll() {
    return this.userService.findAll();
  }

  @Post()
  create(@Body() createUserDto: CreateUserDto) {
    return this.userService.create(createUserDto);
  }
}
  

์—ฌ๊ธฐ์„œ ์ฃผ๋ชฉํ•  ์ ๋“ค:

  • ๐ŸŽญ @Controller() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ: ์ด ํด๋ž˜์Šค๊ฐ€ ์ปจํŠธ๋กค๋Ÿฌ์ž„์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค. 'users'๋Š” ์ด ์ปจํŠธ๋กค๋Ÿฌ์˜ ๋ผ์šฐํŠธ ์ ‘๋‘์‚ฌ์ž…๋‹ˆ๋‹ค.
  • ๐Ÿ”— ์ƒ์„ฑ์ž ์ฃผ์ž…: UserService๋ฅผ ์ฃผ์ž…๋ฐ›์•„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • ๐Ÿšฆ ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ: @Get(), @Post() ๋“ฑ์˜ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋กœ HTTP ๋ฉ”์„œ๋“œ์™€ ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

๐ŸŒŸ ์„ฑ์žฅ ํฌ์ธํŠธ: ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ์„ค๊ณ„ํ•  ๋•Œ๋Š” ๋‹จ์ผ ์ฑ…์ž„ ์›์น™(Single Responsibility Principle)์„ ์—ผ๋‘์— ๋‘์„ธ์š”. ๊ฐ ์ปจํŠธ๋กค๋Ÿฌ๋Š” ํŠน์ • ๋„๋ฉ”์ธ์ด๋‚˜ ๋ฆฌ์†Œ์Šค์— ๋Œ€ํ•œ ์ž‘์—…๋งŒ์„ ์ฒ˜๋ฆฌํ•˜๋„๋ก ํ•˜๋ฉด ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ๊ณผ ์œ ์ง€๋ณด์ˆ˜์„ฑ์ด ํ–ฅ์ƒ๋ฉ๋‹ˆ๋‹ค.

2.3 ์„œ๋น„์Šค (Services)

์„œ๋น„์Šค๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ๋‹ด๋‹นํ•ฉ๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์กฐ์ž‘, ์™ธ๋ถ€ API ํ˜ธ์ถœ ๋“ฑ์˜ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋ฉฐ, ์ปจํŠธ๋กค๋Ÿฌ์— ์˜ํ•ด ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.


import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';

@Injectable()
export class UserService {
  private users = [];

  findAll() {
    return this.users;
  }

  create(createUserDto: CreateUserDto) {
    this.users.push(createUserDto);
    return createUserDto;
  }
}
  

์„œ๋น„์Šค์˜ ์ฃผ์š” ํŠน์ง•:

  • ๐Ÿ’‰ @Injectable() ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ: ์ด ํด๋ž˜์Šค๊ฐ€ Nest IoC ์ปจํ…Œ์ด๋„ˆ์— ์˜ํ•ด ๊ด€๋ฆฌ๋˜๋Š” ์„œ๋น„์Šค์ž„์„ ๋‚˜ํƒ€๋ƒ…๋‹ˆ๋‹ค.
  • ๐Ÿง  ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง: ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํ•ต์‹ฌ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
  • ๐Ÿ”„ ์žฌ์‚ฌ์šฉ์„ฑ: ์—ฌ๋Ÿฌ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ๋™์ผํ•œ ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐Ÿ’ก ์‹ค์ „ ํŒ: ์„œ๋น„์Šค๋ฅผ ์„ค๊ณ„ํ•  ๋•Œ๋Š” ๋‹จ์ผ ์ฑ…์ž„ ์›์น™๊ณผ ํ•จ๊ป˜ ์˜์กด์„ฑ ์ฃผ์ž…(Dependency Injection)์„ ์ ๊ทน ํ™œ์šฉํ•˜์„ธ์š”. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์‰ฝ๊ณ  ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์šฉ์ดํ•œ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์žฌ๋Šฅ๋„ท์˜ ๊ฒฐ์ œ ์„œ๋น„์Šค๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ ์™ธ๋ถ€ ๊ฒฐ์ œ API์™€์˜ ํ†ต์‹ ์„ ๋‹ด๋‹นํ•˜๋Š” ์„œ๋น„์Šค๋ฅผ ๋ณ„๋„๋กœ ๋งŒ๋“ค์–ด ์ฃผ์ž…๋ฐ›์•„ ์‚ฌ์šฉํ•˜๋ฉด, ๋‚˜์ค‘์— ๊ฒฐ์ œ ์‹œ์Šคํ…œ์„ ๋ณ€๊ฒฝํ•˜๋”๋ผ๋„ ์ฝ”๋“œ์˜ ์ˆ˜์ •์„ ์ตœ์†Œํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

2.4 ๋ชจ๋“ˆ, ์ปจํŠธ๋กค๋Ÿฌ, ์„œ๋น„์Šค์˜ ์ƒํ˜ธ์ž‘์šฉ

์ด ์„ธ ๊ฐ€์ง€ ์š”์†Œ๋Š” NestJS ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํ•ต์‹ฌ ๊ตฌ์กฐ๋ฅผ ํ˜•์„ฑํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋“ค์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ์ดํ•ดํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค:

  1. ๋ชจ๋“ˆ์€ ๊ด€๋ จ๋œ ์ปจํŠธ๋กค๋Ÿฌ์™€ ์„œ๋น„์Šค๋ฅผ ๊ทธ๋ฃนํ™”ํ•ฉ๋‹ˆ๋‹ค.
  2. ์ปจํŠธ๋กค๋Ÿฌ๋Š” ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์„ ๋ฐ›์•„ ์ ์ ˆํ•œ ์„œ๋น„์Šค ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
  3. ์„œ๋น„์Šค๋Š” ์‹ค์ œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•˜๊ณ  ๊ฒฐ๊ณผ๋ฅผ ์ปจํŠธ๋กค๋Ÿฌ์— ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  4. ์ปจํŠธ๋กค๋Ÿฌ๋Š” ์„œ๋น„์Šค๋กœ๋ถ€ํ„ฐ ๋ฐ›์€ ๊ฒฐ๊ณผ๋ฅผ ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ์‘๋‹ต์œผ๋กœ ์ „์†กํ•ฉ๋‹ˆ๋‹ค.
NestJS ๊ตฌ์„ฑ์š”์†Œ ์ƒํ˜ธ์ž‘์šฉ NestJS ๊ตฌ์„ฑ์š”์†Œ ์ƒํ˜ธ์ž‘์šฉ ๋ชจ๋“ˆ ์ปจํŠธ๋กค๋Ÿฌ ์„œ๋น„์Šค ๋ฉ”์„œ๋“œ ํ˜ธ์ถœ ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜

์ด๋Ÿฌํ•œ ๊ตฌ์กฐ๋Š” ๊ด€์‹ฌ์‚ฌ์˜ ๋ถ„๋ฆฌ(Separation of Concerns)๋ฅผ ์‹คํ˜„ํ•˜๋ฉฐ, ์ฝ”๋“œ์˜ ์žฌ์‚ฌ์šฉ์„ฑ๊ณผ ํ…Œ์ŠคํŠธ ์šฉ์ด์„ฑ์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚ต๋‹ˆ๋‹ค.

๐ŸŽ“ ํ•™์Šต ํฌ์ธํŠธ: NestJS์˜ ์ด๋Ÿฌํ•œ ๊ตฌ์กฐ์  ํŠน์„ฑ์„ ์ž˜ ํ™œ์šฉํ•˜๋ฉด, ์žฌ๋Šฅ๋„ท๊ณผ ๊ฐ™์€ ๋ณต์žกํ•œ ํ”Œ๋žซํผ๋„ ์ฒด๊ณ„์ ์œผ๋กœ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด:

  • ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ, ์žฌ๋Šฅ ๊ฑฐ๋ž˜, ๊ฒฐ์ œ ์‹œ์Šคํ…œ ๋“ฑ์„ ๊ฐ๊ฐ์˜ ๋ชจ๋“ˆ๋กœ ๊ตฌ์„ฑ
  • ๊ฐ ๋ชจ๋“ˆ ๋‚ด์—์„œ ๊ด€๋ จ ์ปจํŠธ๋กค๋Ÿฌ์™€ ์„œ๋น„์Šค๋ฅผ ์ •์˜
  • ๊ณตํ†ต ๊ธฐ๋Šฅ(์˜ˆ: ์ธ์ฆ, ๋กœ๊น…)์€ ๋ณ„๋„์˜ ๋ชจ๋“ˆ๋กœ ๋งŒ๋“ค์–ด ์žฌ์‚ฌ์šฉ

์ด๋ ‡๊ฒŒ ๊ตฌ์„ฑํ•˜๋ฉด ๊ธฐ๋Šฅ ์ถ”๊ฐ€๋‚˜ ์ˆ˜์ •์ด ํ•„์š”ํ•  ๋•Œ ํ•ด๋‹น ๋ชจ๋“ˆ๋งŒ ์ง‘์ค‘์ ์œผ๋กœ ์ž‘์—…ํ•  ์ˆ˜ ์žˆ์–ด ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์šฉ์ดํ•ด์ง‘๋‹ˆ๋‹ค.

2.5 ์‹ค์ „ ์˜ˆ์ œ: ์žฌ๋Šฅ๋„ท์˜ ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ ๋ชจ๋“ˆ

์ด์ œ ์šฐ๋ฆฌ๊ฐ€ ๋ฐฐ์šด ๊ฐœ๋…์„ ๋ฐ”ํƒ•์œผ๋กœ ์žฌ๋Šฅ๋„ท์˜ ์‚ฌ์šฉ์ž ๊ด€๋ฆฌ ๋ชจ๋“ˆ์„ ๊ฐ„๋‹จํžˆ ๊ตฌํ˜„ํ•ด ๋ณผ๊นŒ์š”?


// user.module.ts
import { Module } from '@nestjs/common';
import { UserController } from './user.controller';
import { UserService } from './user.service';

@Module({
  controllers: [UserController],
  providers: [UserService],
})
export class UserModule {}

// user.controller.ts
import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { UserService } from './user.service';
import { CreateUserDto } from './dto/create-user.dto';

@Controller('users')
export class UserController {
  constructor(private readonly userService: UserService) {}

  @Post()
  create(@Body() createUserDto: CreateUserDto) {
    return this.userService.create(createUserDto);
  }

  @Get(':id')
  findOne(@Param('id') id: string) {
    return this.userService.findOne(id);
  }
}

// user.service.ts
import { Injectable } from '@nestjs/common';
import { CreateUserDto } from './dto/create-user.dto';

@Injectable()
export class UserService {
  private users = [];

  create(createUserDto: CreateUserDto) {
    const newUser = { id: Date.now().toString(), ...createUserDto };
    this.users.push(newUser);
    return newUser;
  }

  findOne(id: string) {
    return this.users.find(user => user.id === id);
  }
}

// create-user.dto.ts
export class CreateUserDto {
  readonly name: string;
  readonly email: string;
  readonly skills: string[];
}
  

์ด ์˜ˆ์ œ์—์„œ:

  • ๐Ÿงฉ UserModule์€ UserController์™€ UserService๋ฅผ ํ•˜๋‚˜๋กœ ๋ฌถ์Šต๋‹ˆ๋‹ค.
  • ๐ŸŽฎ UserController๋Š” ์‚ฌ์šฉ์ž ์ƒ์„ฑ๊ณผ ์กฐํšŒ๋ฅผ ์œ„ํ•œ ์—”๋“œํฌ์ธํŠธ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.
  • ๐Ÿ›  UserService๋Š” ์‹ค์ œ ์‚ฌ์šฉ์ž ๋ฐ์ดํ„ฐ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋กœ์ง์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
  • ๐Ÿ“ CreateUserDto๋Š” ์‚ฌ์šฉ์ž ์ƒ์„ฑ ์‹œ ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ์˜ ๊ตฌ์กฐ๋ฅผ ์ •์˜ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ’ก ์‹ค์ „ ํŒ: ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™, ์ธ์ฆ ๋ฐ ์ธ๊ฐ€, ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋“ฑ ์ถ”๊ฐ€์ ์ธ ๊ธฐ๋Šฅ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. NestJS๋Š” ์ด๋Ÿฌํ•œ ๊ธฐ๋Šฅ๋“ค์„ ์‰ฝ๊ฒŒ ํ†ตํ•ฉํ•  ์ˆ˜ ์žˆ๋Š” ๋‹ค์–‘ํ•œ ๋ชจ๋“ˆ๊ณผ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, TypeORM์„ ์‚ฌ์šฉํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๋™, Passport๋ฅผ ์ด์šฉํ•œ ์ธ์ฆ, ๊ธ€๋กœ๋ฒŒ ์˜ˆ์™ธ ํ•„ํ„ฐ๋ฅผ ํ†ตํ•œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ ๋“ฑ์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ NestJS์˜ ํ•ต์‹ฌ ๊ฐœ๋…์ธ ๋ชจ๋“ˆ, ์ปจํŠธ๋กค๋Ÿฌ, ์„œ๋น„์Šค์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์ด ๊ตฌ์กฐ๋ฅผ ์ž˜ ํ™œ์šฉํ•˜๋ฉด ํ™•์žฅ ๊ฐ€๋Šฅํ•˜๊ณ  ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์‰ฌ์šด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ์„น์…˜์—์„œ๋Š” NestJS์˜ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ๋“ค์„ ์‚ดํŽด๋ณด๋ฉฐ, ๋” ๋ณต์žกํ•œ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ์–ด๋–ป๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. Ready for more? Let's go! ๐Ÿš€

3. NestJS์˜ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ: ๋ฏธ๋“ค์›จ์–ด, ํŒŒ์ดํ”„, ๊ฐ€๋“œ, ์ธํ„ฐ์…‰ํ„ฐ ๐Ÿ›ก๏ธ

NestJS์˜ ์ง„์ •ํ•œ ํž˜์€ ๊ทธ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ๋“ค์— ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ์„น์…˜์—์„œ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋™์ž‘์„ ์„ธ๋ฐ€ํ•˜๊ฒŒ ์ œ์–ดํ•˜๊ณ  ํ™•์žฅํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๋ฏธ๋“ค์›จ์–ด, ํŒŒ์ดํ”„, ๊ฐ€๋“œ, ์ธํ„ฐ์…‰ํ„ฐ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด ๊ธฐ๋Šฅ๋“ค์„ ๋งˆ์Šคํ„ฐํ•˜๋ฉด, ์—ฌ๋Ÿฌ๋ถ„์€ ์ง„์ •ํ•œ NestJS ์ „๋ฌธ๊ฐ€๊ฐ€ ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค! ๐ŸŽ“

3.1 ๋ฏธ๋“ค์›จ์–ด (Middleware)

๋ฏธ๋“ค์›จ์–ด๋Š” ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ ์ „์— ํ˜ธ์ถœ๋˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. ์š”์ฒญ ๋ฐ ์‘๋‹ต ๊ฐ์ฒด์— ์ ‘๊ทผํ•˜๊ณ , ์š”์ฒญ-์‘๋‹ต ์ฃผ๊ธฐ๋ฅผ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐ŸŽ“ ํ•ต์‹ฌ ๊ฐœ๋…: ๋ฏธ๋“ค์›จ์–ด๋Š” ๋กœ๊น…, ์š”์ฒญ ํŒŒ์‹ฑ, ์ธ์ฆ ๋“ฑ ๋‹ค์–‘ํ•œ ์šฉ๋„๋กœ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Express์˜ ๋ฏธ๋“ค์›จ์–ด์™€ ์œ ์‚ฌํ•˜์ง€๋งŒ, NestJS์˜ ์˜์กด์„ฑ ์ฃผ์ž… ์‹œ์Šคํ…œ๊ณผ ํ†ตํ•ฉ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐ„๋‹จํ•œ ๋กœ๊น… ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋งŒ๋“ค์–ด ๋ณผ๊นŒ์š”?


import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log(`Request... ${req.method} ${req.url}`);
    next();
  }
}
  

์ด ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๋ชจ๋“ˆ์— ์ ์šฉํ•˜๋ ค๋ฉด:


import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './logger.middleware';
import { UserController } from './user.controller';

@Module({
  controllers: [UserController],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('users');
  }
}
  

๐Ÿ’ก ์‹ค์ „ ํŒ: ์žฌ๋Šฅ๋„ท์—์„œ ๋ฏธ๋“ค์›จ์–ด๋ฅผ ํ™œ์šฉํ•˜๋ฉด ๋ชจ๋“  API ์š”์ฒญ์— ๋Œ€ํ•œ ๋กœ๊น…, ์‚ฌ์šฉ์ž ์ธ์ฆ ์ƒํƒœ ํ™•์ธ, ์š”์ฒญ ๋ณธ๋ฌธ ํŒŒ์‹ฑ ๋“ฑ์„ ํšจ๊ณผ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ชจ๋“  ์š”์ฒญ์— ๋Œ€ํ•ด ์‚ฌ์šฉ์ž์˜ API ์‚ฌ์šฉ๋Ÿ‰์„ ์ถ”์ ํ•˜๋Š” ๋ฏธ๋“ค์›จ์–ด๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3.2 ํŒŒ์ดํ”„ (Pipes)

ํŒŒ์ดํ”„๋Š” ์ž…๋ ฅ ๋ฐ์ดํ„ฐ๋ฅผ ์›ํ•˜๋Š” ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ํ•˜๊ฑฐ๋‚˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.

NestJS ํŒŒ์ดํ”„ ์ž‘๋™ ๋ฐฉ์‹ NestJS ํŒŒ์ดํ”„ ์ž‘๋™ ๋ฐฉ์‹ ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ ํŒŒ์ดํ”„ ๋ผ์šฐํŠธ ํ•ธ๋“ค๋Ÿฌ ์›๋ณธ ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜/๊ฒ€์ฆ๋œ ๋ฐ์ดํ„ฐ 1. ๋ฐ์ดํ„ฐ ๋ณ€ํ™˜ 2. ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ

NestJS๋Š” ๋ช‡ ๊ฐ€์ง€ ๋‚ด์žฅ ํŒŒ์ดํ”„๋ฅผ ์ œ๊ณตํ•˜์ง€๋งŒ, ์ปค์Šคํ…€ ํŒŒ์ดํ”„๋ฅผ ๋งŒ๋“ค ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์ˆซ์ž ํ˜•์‹์„ ๊ฒ€์ฆํ•˜๋Š” ํŒŒ์ดํ”„๋ฅผ ๋งŒ๋“ค์–ด ๋ณผ๊นŒ์š”?


import { PipeTransform, Injectable, ArgumentMetadata, BadRequestException } from '@nestjs/common';

@Injectable()
export class ParseIntPipe implements PipeTransform<string number> {
  transform(value: string, metadata: ArgumentMetadata): number {
    const val = parseInt(value, 10);
    if (isNaN(val)) {
      throw new BadRequestException('Validation failed');
    }
    return val;
  }
}
  </string>

์ด ํŒŒ์ดํ”„๋ฅผ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์‚ฌ์šฉํ•˜๋ ค๋ฉด:


@Get(':id')
findOne(@Param('id', ParseIntPipe) id: number) {
  return this.userService.findOne(id);
}
  

๐ŸŒŸ ์„ฑ์žฅ ํฌ์ธํŠธ: ํŒŒ์ดํ”„๋ฅผ ์ž˜ ํ™œ์šฉํ•˜๋ฉด ์ž…๋ ฅ ๋ฐ์ดํ„ฐ์˜ ์•ˆ์ •์„ฑ์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์žฌ๋Šฅ๋„ท์—์„œ ์‚ฌ์šฉ์ž๊ฐ€ ์ œ๊ณตํ•˜๋Š” ์ •๋ณด(์˜ˆ: ๊ฐ€๊ฒฉ, ๋‚ ์งœ)๋ฅผ ์ž๋™์œผ๋กœ ์ ์ ˆํ•œ ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ํ•˜๊ณ  ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•˜๋Š” ๋ฐ ํŒŒ์ดํ”„๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3.3 ๊ฐ€๋“œ (Guards)

๊ฐ€๋“œ๋Š” ์ฃผ๋กœ ์ธ์ฆ๊ณผ ์ธ๊ฐ€๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. ํŠน์ • ๋ผ์šฐํŠธ์— ๋Œ€ํ•œ ์ ‘๊ทผ์„ ์ œ์–ดํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';

@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(
    context: ExecutionContext,
  ): boolean | Promise<boolean> | Observable<boolean> {
    const request = context.switchToHttp().getRequest();
    return validateRequest(request);
  }
}

function validateRequest(request: any): boolean {
  // ์—ฌ๊ธฐ์— ์‹ค์ œ ์ธ์ฆ ๋กœ์ง์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
  return true; // ๋˜๋Š” false
}
  </boolean></boolean>

๊ฐ€๋“œ๋ฅผ ์ปจํŠธ๋กค๋Ÿฌ๋‚˜ ๋ฉ”์„œ๋“œ์— ์ ์šฉํ•˜๋ ค๋ฉด:


@UseGuards(AuthGuard)
@Controller('users')
export class UserController {
  // ...
}
  

๐Ÿ’ก ์‹ค์ „ ํŒ: ์žฌ๋Šฅ๋„ท์—์„œ ๊ฐ€๋“œ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํŠน์ • API์— ๋Œ€ํ•œ ์ ‘๊ทผ์„ ์ œํ•œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํ”„๋ฆฌ๋ฏธ์—„ ์‚ฌ์šฉ์ž๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋Š” ํŠน๋ณ„ ๊ธฐ๋Šฅ์ด๋‚˜ ๊ด€๋ฆฌ์ž ์ „์šฉ API๋ฅผ ๊ตฌํ˜„ํ•  ๋•Œ ๊ฐ€๋“œ๋ฅผ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3.4 ์ธํ„ฐ์…‰ํ„ฐ (Interceptors)

์ธํ„ฐ์…‰ํ„ฐ๋Š” ์š”์ฒญ๊ณผ ์‘๋‹ต์„ ๊ฐ€๋กœ์ฑ„๊ณ  ๋ณ€ํ˜•ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ์ž…๋‹ˆ๋‹ค. ๋กœ๊น…, ์บ์‹ฑ, ์‘๋‹ต ๋ณ€ํ˜• ๋“ฑ์— ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';

@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    console.log('Before...');

    const now = Date.now();
    return next
      .handle()
      .pipe(
        tap(() => console.log(`After... ${Date.now() - now}ms`)),
      );
  }
}
  </any>

์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ์ ์šฉํ•˜๋ ค๋ฉด:


@UseInterceptors(LoggingInterceptor)
@Controller('users')
export class UserController {
  // ...
}
  

๐ŸŽ“ ํ•™์Šต ํฌ์ธํŠธ: ์ธํ„ฐ์…‰ํ„ฐ๋Š” ํšก๋‹จ ๊ด€์‹ฌ์‚ฌ(cross-cutting concerns)๋ฅผ ์šฐ์•„ํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ค๋‹ˆ๋‹ค. ์žฌ๋Šฅ๋„ท์—์„œ ์ธํ„ฐ์…‰ํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ชจ๋“  ์‘๋‹ต์— ๊ณตํ†ต ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜, API ํ˜ธ์ถœ ์‹œ๊ฐ„์„ ์ธก์ •ํ•˜์—ฌ ์„ฑ๋Šฅ์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๋Š” ๋“ฑ์˜ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

3.5 ์‹ค์ „ ์˜ˆ์ œ: ์žฌ๋Šฅ๋„ท์˜ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ ๊ตฌํ˜„

์ด์ œ ์šฐ๋ฆฌ๊ฐ€ ๋ฐฐ์šด ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ๋“ค์„ ํ™œ์šฉํ•˜์—ฌ ์žฌ๋Šฅ๋„ท์˜ ์ผ๋ถ€ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด ๋ณผ๊นŒ์š”?


// auth.guard.ts
@Injectable()
export class AuthGuard implements CanActivate {
  canActivate(context: ExecutionContext): boolean {
    const request = context.switchToHttp().getRequest();
    return request.headers.authorization === 'Bearer valid-token';
  }
}

// logging.interceptor.ts
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
  intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
    const request = context.switchToHttp().getRequest();
    const method = request.method;
    const url = request.url;

    console.log(`[${method}] ${url} - ${new Date().toISOString()}`);

    return next.handle().pipe(
      tap(() => console.log(`[${method}] ${url} - Completed`))
    );
  }
}

// validation.pipe.ts
@Injectable()
export class ValidationPipe implements PipeTransform {
  transform(value: any, metadata: ArgumentMetadata) {
    if (!value.name || typeof value.name !== 'string') {
      throw new BadRequestException('Invalid name');
    }
    if (!value.price || typeof value.price !== 'number') {
      throw new BadRequestException('Invalid price');
    }
    return value;
  }
}

// talent.controller.ts
@Controller('talents')
@UseGuards(AuthGuard)
@UseInterceptors(LoggingInterceptor)
export class TalentController {
  @Post()
  createTalent(@Body(ValidationPipe) createTalentDto: CreateTalentDto) {
    // ์žฌ๋Šฅ ์ƒ์„ฑ ๋กœ์ง
  }

  @Get()
  findAllTalents() {
    // ๋ชจ๋“  ์žฌ๋Šฅ ์กฐํšŒ ๋กœ์ง
  }
}
  </any>

์ด ์˜ˆ์ œ์—์„œ:

  • ๐Ÿ›ก๏ธ AuthGuard๋Š” ๋ชจ๋“  ์žฌ๋Šฅ ๊ด€๋ จ API์— ๋Œ€ํ•œ ์ธ์ฆ์„ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • ๐Ÿ“ LoggingInterceptor๋Š” ๋ชจ๋“  ์š”์ฒญ๊ณผ ์‘๋‹ต์„ ๋กœ๊น…ํ•ฉ๋‹ˆ๋‹ค.
  • ๐Ÿ” ValidationPipe๋Š” ์žฌ๋Šฅ ์ƒ์„ฑ ์‹œ ์ž…๋ ฅ ๋ฐ์ดํ„ฐ์˜ ์œ ํšจ์„ฑ์„ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค.

๐ŸŒŸ ์„ฑ์žฅ ํฌ์ธํŠธ: ์ด๋Ÿฌํ•œ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ๋“ค์„ ์กฐํ•ฉํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋ฉด, ์•ˆ์ „ํ•˜๊ณ  ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์šฉ์ดํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์žฌ๋Šฅ๋„ท๊ณผ ๊ฐ™์€ ๋ณต์žกํ•œ ํ”Œ๋žซํผ์—์„œ๋Š” ์ด๋Ÿฌํ•œ ๊ธฐ๋Šฅ๋“ค์ด ์ฝ”๋“œ์˜ ํ’ˆ์งˆ๊ณผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์•ˆ์ •์„ฑ์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ NestJS์˜ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ๋“ค์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์ด ๊ธฐ๋Šฅ๋“ค์„ ๋งˆ์Šคํ„ฐํ•˜๋ฉด, ์—ฌ๋Ÿฌ๋ถ„์€ ๋” ๊ฐ•๋ ฅํ•˜๊ณ  ์œ ์—ฐํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋‹ค์Œ ์„น์…˜์—์„œ๋Š” NestJS์˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ†ตํ•ฉ๊ณผ ORM ์‚ฌ์šฉ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. Ready to dive deeper? Let's go! ๐ŸŠโ€โ™‚๏ธ

4. NestJS์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค: TypeORM์„ ํ™œ์šฉํ•œ ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ ๐Ÿ—ƒ๏ธ

์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํ•ต์‹ฌ์€ ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ์ž…๋‹ˆ๋‹ค. NestJS๋Š” ๋‹ค์–‘ํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ORM(Object-Relational Mapping)์„ ์ง€์›ํ•˜์ง€๋งŒ, ์ด ์„น์…˜์—์„œ๋Š” ํŠนํžˆ TypeORM๊ณผ์˜ ํ†ตํ•ฉ์— ์ดˆ์ ์„ ๋งž์ถ”๊ฒ ์Šต๋‹ˆ๋‹ค. TypeORM์€ TypeScript์™€ ์ž˜ ์–ด์šธ๋ฆฌ๋ฉฐ, NestJS์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜๊ธฐ์— ๋งค์šฐ ์ ํ•ฉํ•ฉ๋‹ˆ๋‹ค. ๐Ÿค

4.1 TypeORM ์†Œ๊ฐœ

TypeORM์€ Node.js์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ORM ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ, TypeScript๋กœ ์ž‘์„ฑ๋˜์–ด ์žˆ์–ด NestJS์™€ ๊ถํ•ฉ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

๐ŸŽ“ ํ•ต์‹ฌ ๊ฐœ๋…: TypeORM์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํ…Œ์ด๋ธ”์„ TypeScript ํด๋ž˜์Šค๋กœ ํ‘œํ˜„ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ž‘์—…์„ ๊ฐ์ฒด ์ง€ํ–ฅ์ ์œผ๋กœ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4.2 NestJS์— TypeORM ํ†ตํ•ฉํ•˜๊ธฐ

๋จผ์ €, ํ•„์š”ํ•œ ํŒจํ‚ค์ง€๋ฅผ ์„ค์น˜ํ•ฉ์‹œ๋‹ค:


npm install @nestjs/typeorm typeorm mysql2
  

๊ทธ๋ฆฌ๊ณ  AppModule์— TypeORM์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค:


import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
  imports: [
    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'password',
      database: 'talentnet',
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
      synchronize: true,
    }),
  ],
})
export class AppModule {}
  

โš ๏ธ ์ฃผ์˜: production ํ™˜๊ฒฝ์—์„œ๋Š” synchronize: true ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜์ง€ ๋งˆ์„ธ์š”. ์ด ์˜ต์…˜์€ ๊ฐœ๋ฐœ ์ค‘์—๋Š” ํŽธ๋ฆฌํ•˜์ง€๋งŒ, ์‹ค์ œ ์šด์˜ ํ™˜๊ฒฝ์—์„œ๋Š” ๋ฐ์ดํ„ฐ ์†์‹ค์˜ ์œ„ํ—˜์ด ์žˆ์Šต๋‹ˆ๋‹ค.

4.3 ์—”ํ‹ฐํ‹ฐ ์ •์˜ํ•˜๊ธฐ

์žฌ๋Šฅ๋„ท์˜ '์žฌ๋Šฅ' ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ •์˜ํ•ด ๋ณผ๊นŒ์š”?


import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm';

@Entity()
export class Talent {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column()
  description: string;

  @Column()
  price: number;

  @Column()
  userId: number;

  @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
  createdAt: Date;
}
  

4.4 ๋ฆฌํฌ์ง€ํ† ๋ฆฌ ์‚ฌ์šฉํ•˜๊ธฐ

TypeORM์˜ ๋ฆฌํฌ์ง€ํ† ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:


import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Talent } from './talent.entity';

@Injectable()
export class TalentService {
  constructor(
    @InjectRepository(Talent)
    private talentRepository: Repository<talent>,
  ) {}

  async create(talent: Partial<talent>): Promise<talent> {
    const newTalent = this.talentRepository.create(talent);
    return this.talentRepository.save(newTalent);
  }

  async findAll(): Promise<talent> {
    return this.talentRepository.find();
  }

  async findOne(id: number): Promise<talent> {
    return this.talentRepository.findOne({ where: { id } });
  }

  async update(id: number, talent: Partial<talent>): Promise<talent> {
    await this.talentRepository.update(id, talent);
    return this.talentRepository.findOne({ where: { id } });
  }

  async remove(id: number): Promise<void> {
    await this.talentRepository.delete(id);
  }
}
  </void></talent></talent></talent></talent></talent></talent></talent>

๐Ÿ’ก ์‹ค์ „ ํŒ: ๋ณต์žกํ•œ ์ฟผ๋ฆฌ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ, TypeORM์˜ QueryBuilder๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ํŠน์ • ๊ฐ€๊ฒฉ ๋ฒ”์œ„ ๋‚ด์˜ ์žฌ๋Šฅ์„ ๊ฒ€์ƒ‰ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•  ๋•Œ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.

4.5 ๊ด€๊ณ„ ์„ค์ •ํ•˜๊ธฐ

์žฌ๋Šฅ๊ณผ ์‚ฌ์šฉ์ž ์‚ฌ์ด์˜ ๊ด€๊ณ„๋ฅผ ์„ค์ •ํ•ด ๋ด…์‹œ๋‹ค:


// user.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, OneToMany } from 'typeorm';
import { Talent } from './talent.entity';

@Entity()
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  name: string;

  @OneToMany(() => Talent, talent => talent.user)
  talents: Talent[];
}

// talent.entity.ts
import { Entity, PrimaryGeneratedColumn, Column, ManyToOne } from 'typeorm';
import { User } from './user.entity';

@Entity()
export class Talent {
  // ... ๊ธฐ์กด ํ•„๋“œ๋“ค

  @ManyToOne(() => User, user => user.talents)
  user: User;
}
  

4.6 ํŠธ๋žœ์žญ์…˜ ์‚ฌ์šฉํ•˜๊ธฐ4.6 ํŠธ๋žœ์žญ์…˜ ์‚ฌ์šฉํ•˜๊ธฐ

๋ฐ์ดํ„ฐ์˜ ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•˜๊ธฐ ์œ„ํ•ด ํŠธ๋žœ์žญ์…˜์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. TypeORM์—์„œ๋Š” ํŠธ๋žœ์žญ์…˜์„ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository, Connection } from 'typeorm';
import { Talent } from './talent.entity';
import { User } from './user.entity';

@Injectable()
export class TalentService {
  constructor(
    @InjectRepository(Talent)
    private talentRepository: Repository<talent>,
    @InjectRepository(User)
    private userRepository: Repository<user>,
    private connection: Connection,
  ) {}

  async createTalentWithUser(talentData: Partial<talent>, userId: number): Promise<talent> {
    const queryRunner = this.connection.createQueryRunner();

    await queryRunner.connect();
    await queryRunner.startTransaction();

    try {
      const user = await this.userRepository.findOne({ where: { id: userId } });
      if (!user) {
        throw new Error('User not found');
      }

      const talent = this.talentRepository.create(talentData);
      talent.user = user;

      await queryRunner.manager.save(talent);

      await queryRunner.commitTransaction();

      return talent;
    } catch (err) {
      await queryRunner.rollbackTransaction();
      throw err;
    } finally {
      await queryRunner.release();
    }
  }
}
  </talent></talent></user></talent>

โš ๏ธ ์ฃผ์˜: ํŠธ๋žœ์žญ์…˜์„ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ํ•ญ์ƒ ์—๋Ÿฌ ์ฒ˜๋ฆฌ์™€ ๋กค๋ฐฑ์„ ๊ณ ๋ คํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์œ„ ์˜ˆ์ œ์—์„œ๋Š” try-catch ๋ธ”๋ก์„ ์‚ฌ์šฉํ•˜์—ฌ ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ ํŠธ๋žœ์žญ์…˜์„ ๋กค๋ฐฑํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.

4.7 ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ด€๋ฆฌ

ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ๋Š” ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ์„ ์•ˆ์ „ํ•˜๊ฒŒ ๊ด€๋ฆฌํ•˜๊ธฐ ์œ„ํ•ด ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์‚ฌ์šฉํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. TypeORM์€ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.


// ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์ƒ์„ฑ
npx typeorm migration:create -n CreateTalentTable

// ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹คํ–‰
npx typeorm migration:run

// ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋˜๋Œ๋ฆฌ๊ธฐ
npx typeorm migration:revert
  

๐Ÿ’ก ์‹ค์ „ ํŒ: ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ๋ฒ„์ „ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์— ํฌํ•จ์‹œํ‚ค์„ธ์š”. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ํŒ€์›๋“ค๊ณผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์Šคํ‚ค๋งˆ ๋ณ€๊ฒฝ ๋‚ด์—ญ์„ ๊ณต์œ ํ•˜๊ณ  ์ถ”์ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

4.8 ์„ฑ๋Šฅ ์ตœ์ ํ™”

๋Œ€๊ทœ๋ชจ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์—์„œ๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ ์„ฑ๋Šฅ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. TypeORM์—์„œ ์„ฑ๋Šฅ์„ ์ตœ์ ํ™”ํ•˜๋Š” ๋ช‡ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

  1. ์ธ๋ฑ์Šค ์‚ฌ์šฉ: ์ž์ฃผ ๊ฒ€์ƒ‰๋˜๋Š” ํ•„๋“œ์— ์ธ๋ฑ์Šค๋ฅผ ์ถ”๊ฐ€ํ•ฉ๋‹ˆ๋‹ค.
  2. 
    @Entity()
    export class Talent {
      // ...
    
      @Index()
      @Column()
      title: string;
    
      // ...
    }
        
  3. Eager/Lazy ๋กœ๋”ฉ ๊ด€๋ฆฌ: ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ ๊ด€๊ณ„๋ฅผ ๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค.
  4. 
    @Entity()
    export class User {
      // ...
    
      @OneToMany(() => Talent, talent => talent.user, { lazy: true })
      talents: Promise<talent>;
    }
        </talent>
  5. ํŽ˜์ด์ง€๋„ค์ด์…˜ ๊ตฌํ˜„: ๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ ๋ฒˆ์— ๋กœ๋“œํ•˜์ง€ ์•Š๋„๋ก ํ•ฉ๋‹ˆ๋‹ค.
  6. 
    async findTalents(page: number, limit: number): Promise<talent> {
      return this.talentRepository.find({
        skip: (page - 1) * limit,
        take: limit,
      });
    }
        </talent>

๐ŸŒŸ ์„ฑ์žฅ ํฌ์ธํŠธ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ฑ๋Šฅ ์ตœ์ ํ™”๋Š” ์ง€์†์ ์ธ ๊ณผ์ •์ž…๋‹ˆ๋‹ค. ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์‚ฌ์šฉ ํŒจํ„ด์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•˜๊ณ , ํ•„์š”์— ๋”ฐ๋ผ ์ฟผ๋ฆฌ์™€ ์Šคํ‚ค๋งˆ๋ฅผ ์ตœ์ ํ™”ํ•˜์„ธ์š”. ์žฌ๋Šฅ๋„ท๊ณผ ๊ฐ™์€ ํ”Œ๋žซํผ์—์„œ๋Š” ์‚ฌ์šฉ์ž ์ˆ˜๊ฐ€ ์ฆ๊ฐ€ํ•จ์— ๋”ฐ๋ผ ์„ฑ๋Šฅ ์ตœ์ ํ™”๊ฐ€ ๋”์šฑ ์ค‘์š”ํ•ด์ง‘๋‹ˆ๋‹ค.

4.9 ์‹ค์ „ ์˜ˆ์ œ: ์žฌ๋Šฅ๋„ท์˜ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ ๊ตฌํ˜„

์ด์ œ ์šฐ๋ฆฌ๊ฐ€ ๋ฐฐ์šด ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ์žฌ๋Šฅ๋„ท์˜ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด ๋ณผ๊นŒ์š”?


import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Talent } from './talent.entity';

@Injectable()
export class TalentService {
  constructor(
    @InjectRepository(Talent)
    private talentRepository: Repository<talent>,
  ) {}

  async searchTalents(query: string, page: number = 1, limit: number = 10): Promise<{ talents: Talent[], total: number }> {
    const [talents, total] = await this.talentRepository.createQueryBuilder('talent')
      .where('talent.title LIKE :query OR talent.description LIKE :query', { query: `%${query}%` })
      .leftJoinAndSelect('talent.user', 'user')
      .orderBy('talent.createdAt', 'DESC')
      .skip((page - 1) * limit)
      .take(limit)
      .getManyAndCount();

    return { talents, total };
  }
}
  </talent>

์ด ์˜ˆ์ œ์—์„œ๋Š”:

  • ์ œ๋ชฉ์ด๋‚˜ ์„ค๋ช…์— ๊ฒ€์ƒ‰์–ด๊ฐ€ ํฌํ•จ๋œ ์žฌ๋Šฅ์„ ์ฐพ์Šต๋‹ˆ๋‹ค.
  • ์žฌ๋Šฅ๊ณผ ์—ฐ๊ด€๋œ ์‚ฌ์šฉ์ž ์ •๋ณด๋„ ํ•จ๊ป˜ ๊ฐ€์ ธ์˜ต๋‹ˆ๋‹ค.
  • ๊ฒฐ๊ณผ๋ฅผ ์ตœ์‹ ์ˆœ์œผ๋กœ ์ •๋ ฌํ•ฉ๋‹ˆ๋‹ค.
  • ํŽ˜์ด์ง€๋„ค์ด์…˜์„ ๊ตฌํ˜„ํ•˜์—ฌ ์„ฑ๋Šฅ์„ ๊ฐœ์„ ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ’ก ์‹ค์ „ ํŒ: ์‹ค์ œ ์„œ๋น„์Šค์—์„œ๋Š” ์ „๋ฌธ ๊ฒ€์ƒ‰ ์—”์ง„(์˜ˆ: Elasticsearch)์„ ๋„์ž…ํ•˜๋Š” ๊ฒƒ์„ ๊ณ ๋ คํ•ด๋ณด์„ธ์š”. ๋Œ€๊ทœ๋ชจ ๋ฐ์ดํ„ฐ์—์„œ์˜ ์ „๋ฌธ ๊ฒ€์ƒ‰, ํ•„ํ„ฐ๋ง, ์ •๋ ฌ ๋“ฑ์„ ๋” ํšจ์œจ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ NestJS์™€ TypeORM์„ ์‚ฌ์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ํšจ๊ณผ์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ์•„๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ๊ธฐ์ˆ ์„ ๋งˆ์Šคํ„ฐํ•˜๋ฉด, ํ™•์žฅ ๊ฐ€๋Šฅํ•˜๊ณ  ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์šฉ์ดํ•œ ๋ฐฑ์—”๋“œ ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ ์„น์…˜์—์„œ๋Š” NestJS์—์„œ์˜ ํ…Œ์ŠคํŒ…๊ณผ ๋ฐฐํฌ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. Ready to level up your NestJS skills even further? Let's go! ๐Ÿš€

5. NestJS ํ…Œ์ŠคํŒ…๊ณผ ๋ฐฐํฌ: ํ’ˆ์งˆ๊ณผ ์•ˆ์ •์„ฑ ํ™•๋ณดํ•˜๊ธฐ ๐Ÿงช๐Ÿš€

์†Œํ”„ํŠธ์›จ์–ด ๊ฐœ๋ฐœ์—์„œ ํ…Œ์ŠคํŒ…๊ณผ ๋ฐฐํฌ๋Š” ๋งค์šฐ ์ค‘์š”ํ•œ ๊ณผ์ •์ž…๋‹ˆ๋‹ค. ์ด ์„น์…˜์—์„œ๋Š” NestJS ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ๋ฐฉ๋ฒ•๊ณผ ํšจ๊ณผ์ ์ธ ๋ฐฐํฌ ์ „๋žต์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์žฌ๋Šฅ๋„ท๊ณผ ๊ฐ™์€ ๋ณต์žกํ•œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํ’ˆ์งˆ๊ณผ ์•ˆ์ •์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๐Ÿ› ๏ธ

5.1 NestJS ํ…Œ์ŠคํŒ… ๊ธฐ์ดˆ

NestJS๋Š” Jest๋ฅผ ๊ธฐ๋ณธ ํ…Œ์ŠคํŒ… ํ”„๋ ˆ์ž„์›Œํฌ๋กœ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์œ ๋‹› ํ…Œ์ŠคํŠธ, ํ†ตํ•ฉ ํ…Œ์ŠคํŠธ, e2e ํ…Œ์ŠคํŠธ ๋“ฑ ๋‹ค์–‘ํ•œ ์œ ํ˜•์˜ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๐ŸŽ“ ํ•ต์‹ฌ ๊ฐœ๋…: ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ(TDD)์„ ์‹ค์ฒœํ•˜๋ฉด ์ฝ”๋“œ์˜ ํ’ˆ์งˆ์„ ๋†’์ด๊ณ  ๋ฒ„๊ทธ๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. NestJS์˜ ๋ชจ๋“ˆํ™”๋œ ๊ตฌ์กฐ๋Š” ๋‹จ์œ„ ํ…Œ์ŠคํŠธ ์ž‘์„ฑ์„ ์šฉ์ดํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค.

5.2 ์œ ๋‹› ํ…Œ์ŠคํŠธ ์ž‘์„ฑํ•˜๊ธฐ

์„œ๋น„์Šค ๋กœ์ง์— ๋Œ€ํ•œ ์œ ๋‹› ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•ด ๋ด…์‹œ๋‹ค:


// talent.service.spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { getRepositoryToken } from '@nestjs/typeorm';
import { TalentService } from './talent.service';
import { Talent } from './talent.entity';

describe('TalentService', () => {
  let service: TalentService;
  let mockRepository;

  beforeEach(async () => {
    mockRepository = {
      find: jest.fn(),
      create: jest.fn(),
      save: jest.fn(),
    };

    const module: TestingModule = await Test.createTestingModule({
      providers: [
        TalentService,
        {
          provide: getRepositoryToken(Talent),
          useValue: mockRepository,
        },
      ],
    }).compile();

    service = module.get<talentservice>(TalentService);
  });

  it('should be defined', () => {
    expect(service).toBeDefined();
  });

  describe('findAll', () => {
    it('should return an array of talents', async () => {
      const result = [{ id: 1, title: 'Test Talent' }];
      mockRepository.find.mockResolvedValue(result);

      expect(await service.findAll()).toBe(result);
    });
  });
});
  </talentservice>

๐Ÿ’ก ์‹ค์ „ ํŒ: ๋ชจ๋“  ์—ฃ์ง€ ์ผ€์ด์Šค๋ฅผ ๊ณ ๋ คํ•˜์—ฌ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜์„ธ์š”. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฐ์ดํ„ฐ๊ฐ€ ์—†๋Š” ๊ฒฝ์šฐ, ์ž˜๋ชป๋œ ์ž…๋ ฅ์ด ์ฃผ์–ด์ง„ ๊ฒฝ์šฐ ๋“ฑ์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ๋„ ํฌํ•จ์‹œํ‚ค๋ฉด ์ข‹์Šต๋‹ˆ๋‹ค.

5.3 e2e ํ…Œ์ŠคํŠธ ์ž‘์„ฑํ•˜๊ธฐ

e2e(end-to-end) ํ…Œ์ŠคํŠธ๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ „์ฒด ํ๋ฆ„์„ ํ…Œ์ŠคํŠธํ•ฉ๋‹ˆ๋‹ค:


// test/talent.e2e-spec.ts
import { Test, TestingModule } from '@nestjs/testing';
import { INestApplication } from '@nestjs/common';
import * as request from 'supertest';
import { AppModule } from './../src/app.module';

describe('TalentController (e2e)', () => {
  let app: INestApplication;

  beforeEach(async () => {
    const moduleFixture: TestingModule = await Test.createTestingModule({
      imports: [AppModule],
    }).compile();

    app = moduleFixture.createNestApplication();
    await app.init();
  });

  it('/talents (GET)', () => {
    return request(app.getHttpServer())
      .get('/talents')
      .expect(200)
      .expect('Content-Type', /json/)
      .expect(res => {
        expect(Array.isArray(res.body)).toBeTruthy();
      });
  });

  afterAll(async () => {
    await app.close();
  });
});
  

5.4 ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€ ํ™•์ธ

Jest์˜ ์ปค๋ฒ„๋ฆฌ์ง€ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค:


npm run test:cov
  

๐ŸŒŸ ์„ฑ์žฅ ํฌ์ธํŠธ: ๋†’์€ ํ…Œ์ŠคํŠธ ์ปค๋ฒ„๋ฆฌ์ง€๋ฅผ ์œ ์ง€ํ•˜๋ฉด ์ฝ”๋“œ ๋ณ€๊ฒฝ ์‹œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋Š” ํšŒ๊ท€ ์˜ค๋ฅ˜๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์žฌ๋Šฅ๋„ท๊ณผ ๊ฐ™์€ ๋ณต์žกํ•œ ์‹œ์Šคํ…œ์—์„œ๋Š” ํŠนํžˆ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

5.5 CI/CD ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์ถ•

์ง€์†์  ํ†ตํ•ฉ(CI)๊ณผ ์ง€์†์  ๋ฐฐํฌ(CD)๋ฅผ ๊ตฌ์ถ•ํ•˜์—ฌ ๊ฐœ๋ฐœ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ž๋™ํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. GitHub Actions๋ฅผ ์‚ฌ์šฉํ•œ ์˜ˆ์‹œ๋ฅผ ์‚ดํŽด๋ด…์‹œ๋‹ค:


# .github/workflows/main.yml
name: CI/CD

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Use Node.js
      uses: actions/setup-node@v2
      with:
        node-version: '14.x'
    - run: npm ci
    - run: npm run build --if-present
    - run: npm test

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'

    steps:
    - uses: actions/checkout@v2
    - name: Deploy to Heroku
      uses: akhileshns/heroku-deploy@v3.12.12
      with:
        heroku_api_key: ${{secrets.HEROKU_API_KEY}}
        heroku_app_name: "your-app-name"
        heroku_email: "your-email@example.com"
  

โš ๏ธ ์ฃผ์˜: CI/CD ํŒŒ์ดํ”„๋ผ์ธ์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๋น„๋ฐ€ ํ‚ค๋‚˜ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋Š” ์•ˆ์ „ํ•˜๊ฒŒ ๊ด€๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. GitHub Secrets ๋“ฑ์„ ํ™œ์šฉํ•˜์—ฌ ์ค‘์š” ์ •๋ณด๋ฅผ ๋ณดํ˜ธํ•˜์„ธ์š”.

5.6 ๋ฐฐํฌ ์ „๋žต

NestJS ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋ฐฐํฌํ•  ๋•Œ ๊ณ ๋ คํ•ด์•ผ ํ•  ๋ช‡ ๊ฐ€์ง€ ์ „๋žต์ด ์žˆ์Šต๋‹ˆ๋‹ค:

  1. ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์‚ฌ์šฉ: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์—ฐ๊ฒฐ ์ •๋ณด, API ํ‚ค ๋“ฑ์€ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋กœ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  2. PM2 ์‚ฌ์šฉ: ํ”„๋กœ๋•์…˜ ํ™˜๊ฒฝ์—์„œ Node.js ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐ ์œ ์šฉํ•ฉ๋‹ˆ๋‹ค.
  3. ๋กœ๋“œ ๋ฐธ๋Ÿฐ์‹ฑ: ํŠธ๋ž˜ํ”ฝ์ด ๋งŽ์€ ๊ฒฝ์šฐ, ์—ฌ๋Ÿฌ ์ธ์Šคํ„ด์Šค๋ฅผ ์‹คํ–‰ํ•˜๊ณ  ๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  4. ๋ชจ๋‹ˆํ„ฐ๋ง: New Relic, Datadog ๋“ฑ์˜ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„ฑ๋Šฅ์„ ๋ชจ๋‹ˆํ„ฐ๋งํ•ฉ๋‹ˆ๋‹ค.

5.7 ์„ฑ๋Šฅ ์ตœ์ ํ™”

๋ฐฐํฌ ํ›„์—๋„ ์ง€์†์ ์ธ ์„ฑ๋Šฅ ์ตœ์ ํ™”๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค:

  • ์บ์‹ฑ ์ „๋žต ๊ตฌํ˜„ (Redis ๋“ฑ ์‚ฌ์šฉ)
  • ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ ์ตœ์ ํ™”
  • ๋น„๋™๊ธฐ ์ž‘์—… ์ฒ˜๋ฆฌ (Bull.js ๋“ฑ ์‚ฌ์šฉ)
  • CDN ํ™œ์šฉ

๐Ÿ’ก ์‹ค์ „ ํŒ: ์„ฑ๋Šฅ ๋ณ‘๋ชฉ ํ˜„์ƒ์„ ์‹๋ณ„ํ•˜๊ธฐ ์œ„ํ•ด ์ฃผ๊ธฐ์ ์œผ๋กœ ํ”„๋กœํŒŒ์ผ๋ง์„ ์ˆ˜ํ–‰ํ•˜์„ธ์š”. ์žฌ๋Šฅ๋„ท๊ณผ ๊ฐ™์€ ๋Œ€๊ทœ๋ชจ ํ”Œ๋žซํผ์—์„œ๋Š” ์ž‘์€ ์ตœ์ ํ™”๋„ ํฐ ์˜ํ–ฅ์„ ๋ฏธ์น  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

5.8 ๋ณด์•ˆ ๊ณ ๋ ค์‚ฌํ•ญ

์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ณด์•ˆ์€ ๋งค์šฐ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ๋‹ค์Œ ์‚ฌํ•ญ๋“ค์„ ๊ณ ๋ คํ•˜์„ธ์š”:

  • HTTPS ์‚ฌ์šฉ
  • ์ž…๋ ฅ ๋ฐ์ดํ„ฐ ๊ฒ€์ฆ ๋ฐ ์‚ด๊ท 
  • CORS ์„ค์ •
  • Rate limiting ๊ตฌํ˜„
  • ์ •๊ธฐ์ ์ธ ๋ณด์•ˆ ๊ฐ์‚ฌ ์ˆ˜ํ–‰

โš ๏ธ ์ฃผ์˜: ๋ณด์•ˆ์€ ์ง€์†์ ์ธ ๊ณผ์ •์ž…๋‹ˆ๋‹ค. ์ƒˆ๋กœ์šด ์ทจ์•ฝ์ ์ด ๋ฐœ๊ฒฌ๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ, ํ•ญ์ƒ ์ตœ์‹  ๋ณด์•ˆ ์—…๋ฐ์ดํŠธ๋ฅผ ์ ์šฉํ•˜๊ณ  ๋ณด์•ˆ ๋ชจ๋ฒ” ์‚ฌ๋ก€๋ฅผ ๋”ฐ๋ฅด์„ธ์š”.

5.9 ์‹ค์ „ ์˜ˆ์ œ: ์žฌ๋Šฅ๋„ท ๋ฐฐํฌ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

์žฌ๋Šฅ๋„ท์„ ๋ฐฐํฌํ•  ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ฒดํฌ๋ฆฌ์ŠคํŠธ๋ฅผ ๋งŒ๋“ค์–ด ๋ด…์‹œ๋‹ค:


[ ] ๋ชจ๋“  ํ…Œ์ŠคํŠธ ํ†ต๊ณผ ํ™•์ธ
[ ] ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ์„ค์ • (๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค URL, API ํ‚ค ๋“ฑ)
[ ] ํ”„๋กœ๋•์…˜ ๋นŒ๋“œ ์ƒ์„ฑ
[ ] ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹คํ–‰
[ ] PM2 ๋˜๋Š” ์œ ์‚ฌํ•œ ํ”„๋กœ์„ธ์Šค ๊ด€๋ฆฌ์ž ์„ค์ •
[ ] HTTPS ์„ค์ •
[ ] ๋กœ๋“œ ๋ฐธ๋Ÿฐ์„œ ๊ตฌ์„ฑ (ํ•„์š”ํ•œ ๊ฒฝ์šฐ)
[ ] ๋ชจ๋‹ˆํ„ฐ๋ง ๋„๊ตฌ ์„ค์ • (New Relic, Datadog ๋“ฑ)
[ ] ๋กœ๊ทธ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ ์—ฐ๋™
[ ] ๋ฐฑ์—… ์‹œ์Šคํ…œ ํ™•์ธ
[ ] ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ์ˆ˜ํ–‰
[ ] ๋ณด์•ˆ ์Šค์บ” ์‹คํ–‰
[ ] ๋กค๋ฐฑ ๊ณ„ํš ์ˆ˜๋ฆฝ
[ ] ์‚ฌ์šฉ์ž ๋Œ€์ƒ ๊ณต์ง€ ์ค€๋น„ (์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์ด๋‚˜ ๋ณ€๊ฒฝ์‚ฌํ•ญ์ด ์žˆ๋Š” ๊ฒฝ์šฐ)
  

๐ŸŒŸ ์„ฑ์žฅ ํฌ์ธํŠธ: ๋ฐฐํฌ ํ”„๋กœ์„ธ์Šค๋ฅผ ์ž๋™ํ™”ํ•˜๊ณ  ํ‘œ์ค€ํ™”ํ•˜๋ฉด ์ธ์  ์˜ค๋ฅ˜๋ฅผ ์ค„์ด๊ณ  ์ผ๊ด€์„ฑ์„ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์žฌ๋Šฅ๋„ท๊ณผ ๊ฐ™์€ ๋ณต์žกํ•œ ์‹œ์Šคํ…œ์—์„œ๋Š” ์ด๋Ÿฌํ•œ ์ฒด๊ณ„์ ์ธ ์ ‘๊ทผ์ด ํŠนํžˆ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ NestJS ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ํ…Œ์ŠคํŒ…๊ณผ ๋ฐฐํฌ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์‹ค์ฒœ์€ ์žฌ๋Šฅ๋„ท๊ณผ ๊ฐ™์€ ๋Œ€๊ทœ๋ชจ ํ”Œ๋žซํผ์˜ ์•ˆ์ •์„ฑ๊ณผ ์‹ ๋ขฐ์„ฑ์„ ํฌ๊ฒŒ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ ์ž‘์„ฑ๋ถ€ํ„ฐ ๋ฐฐํฌ, ๊ทธ๋ฆฌ๊ณ  ์ง€์†์ ์ธ ๋ชจ๋‹ˆํ„ฐ๋ง๊ณผ ์ตœ์ ํ™”๊นŒ์ง€, ์ „์ฒด ๊ฐœ๋ฐœ ๋ผ์ดํ”„์‚ฌ์ดํด์„ ๊ณ ๋ คํ•œ ์ ‘๊ทผ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. ์ด์ œ ์—ฌ๋Ÿฌ๋ถ„์€ NestJS๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ฒฌ๊ณ ํ•˜๊ณ  ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ๋ฐฑ์—”๋“œ ์‹œ์Šคํ…œ์„ ๊ตฌ์ถ•ํ•  ์ค€๋น„๊ฐ€ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. Happy coding! ๐Ÿš€๐Ÿ‘จโ€๐Ÿ’ป๐Ÿ‘ฉโ€๐Ÿ’ป

6. ๊ฒฐ๋ก : NestJS ๋งˆ์Šคํ„ฐ์˜ ๊ธธ ๐Ÿ†

์ถ•ํ•˜ํ•ฉ๋‹ˆ๋‹ค! ์—ฌ๋Ÿฌ๋ถ„์€ ์ด์ œ NestJS์˜ ํ•ต์‹ฌ ๊ฐœ๋…๋ถ€ํ„ฐ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ, ๊ทธ๋ฆฌ๊ณ  ์‹ค์ œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐœ๋ฐœ๊ณผ ๋ฐฐํฌ๊นŒ์ง€ ์ „๋ฐ˜์ ์ธ ๋‚ด์šฉ์„ ํ•™์Šตํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด ์ง€์‹์„ ๋ฐ”ํƒ•์œผ๋กœ ์žฌ๋Šฅ๋„ท๊ณผ ๊ฐ™์€ ๋ณต์žกํ•œ ํ”Œ๋žซํผ์„ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋ฐ˜์„ ๋งˆ๋ จํ–ˆ์Šต๋‹ˆ๋‹ค. ๐ŸŽ‰

6.1 ํ•ต์‹ฌ ์š”์•ฝ

  • NestJS์˜ ๋ชจ๋“ˆํ™”๋œ ๊ตฌ์กฐ์™€ ์˜์กด์„ฑ ์ฃผ์ž… ์‹œ์Šคํ…œ
  • ์ปจํŠธ๋กค๋Ÿฌ, ์„œ๋น„์Šค, ๋ฏธ๋“ค์›จ์–ด, ํŒŒ์ดํ”„, ๊ฐ€๋“œ, ์ธํ„ฐ์…‰ํ„ฐ์˜ ์—ญํ• ๊ณผ ์‚ฌ์šฉ๋ฒ•
  • TypeORM์„ ํ™œ์šฉํ•œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๊ด€๋ฆฌ
  • ๋‹จ์œ„ ํ…Œ์ŠคํŠธ์™€ e2e ํ…Œ์ŠคํŠธ ์ž‘์„ฑ ๋ฐฉ๋ฒ•
  • CI/CD ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์ถ•๊ณผ ํšจ๊ณผ์ ์ธ ๋ฐฐํฌ ์ „๋žต

๐Ÿ’ก ํ•ต์‹ฌ ํ†ต์ฐฐ: NestJS์˜ ์ง„์ •ํ•œ ๊ฐ•์ ์€ ๊ทธ ๊ตฌ์กฐํ™”๋œ ์•„ํ‚คํ…์ฒ˜์— ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋Œ€๊ทœ๋ชจ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜๋„ ์ฒด๊ณ„์ ์œผ๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ํ™•์žฅํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

6.2 ๋‹ค์Œ ๋‹จ๊ณ„

NestJS ๋งˆ์Šคํ„ฐ๊ฐ€ ๋˜๊ธฐ ์œ„ํ•œ ์—ฌ์ •์€ ์—ฌ๊ธฐ์„œ ๋๋‚˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‹ค์Œ์€ ์—ฌ๋Ÿฌ๋ถ„์ด ๋” ๊นŠ์ด ํƒ๊ตฌํ•  ์ˆ˜ ์žˆ๋Š” ์ฃผ์ œ๋“ค์ž…๋‹ˆ๋‹ค:

  1. GraphQL๊ณผ NestJS ํ†ตํ•ฉ
  2. ๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜ ๊ตฌํ˜„
  3. WebSocket์„ ํ™œ์šฉํ•œ ์‹ค์‹œ๊ฐ„ ๊ธฐ๋Šฅ ๊ฐœ๋ฐœ
  4. ์„œ๋ฒ„๋ฆฌ์Šค ์•„ํ‚คํ…์ฒ˜์™€ NestJS
  5. ๊ณ ๊ธ‰ ๋ณด์•ˆ ๊ธฐ๋ฒ• (OAuth, JWT ๋“ฑ)

6.3 ์‹ค์ „ ํ”„๋กœ์ ํŠธ ์•„์ด๋””์–ด

ํ•™์Šตํ•œ ๋‚ด์šฉ์„ ์‹ค์ œ๋กœ ์ ์šฉํ•ด๋ณด๋Š” ๊ฒƒ์ด ๊ฐ€์žฅ ์ข‹์€ ํ•™์Šต ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. ๋‹ค์Œ๊ณผ ๊ฐ™์€ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ๋„ํ•ด ๋ณด์„ธ์š”:

  • ๋ธ”๋กœ๊ทธ ํ”Œ๋žซํผ ๊ตฌ์ถ• (์‚ฌ์šฉ์ž ์ธ์ฆ, ๊ฒŒ์‹œ๊ธ€ CRUD, ๋Œ“๊ธ€ ๊ธฐ๋Šฅ)
  • ์‹ค์‹œ๊ฐ„ ์ฑ„ํŒ… ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ (WebSocket ํ™œ์šฉ)
  • ์˜จ๋ผ์ธ ์‡ผํ•‘๋ชฐ ๋ฐฑ์—”๋“œ (์ƒํ’ˆ ๊ด€๋ฆฌ, ์žฅ๋ฐ”๊ตฌ๋‹ˆ, ๊ฒฐ์ œ ํ†ตํ•ฉ)
  • API ๊ฒŒ์ดํŠธ์›จ์ด ๊ตฌํ˜„ (๋งˆ์ดํฌ๋กœ์„œ๋น„์Šค ์•„ํ‚คํ…์ฒ˜)

๐ŸŒŸ ์„ฑ์žฅ ์กฐ์–ธ: ์˜คํ”ˆ ์†Œ์Šค ํ”„๋กœ์ ํŠธ์— ๊ธฐ์—ฌํ•˜๋Š” ๊ฒƒ๋„ ์ข‹์€ ํ•™์Šต ๋ฐฉ๋ฒ•์ž…๋‹ˆ๋‹ค. NestJS ์ƒํƒœ๊ณ„์˜ ๋‹ค์–‘ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‚˜ ํ”Œ๋Ÿฌ๊ทธ์ธ์— ๊ธฐ์—ฌํ•˜๋ฉด์„œ ์‹ค์ „ ๊ฒฝํ—˜์„ ์Œ“์•„๋ณด์„ธ์š”.

6.4 ์ปค๋ฎค๋‹ˆํ‹ฐ ์ฐธ์—ฌ

NestJS ์ปค๋ฎค๋‹ˆํ‹ฐ์— ์ฐธ์—ฌํ•˜์—ฌ ์ง€์†์ ์œผ๋กœ ํ•™์Šตํ•˜๊ณ  ์„ฑ์žฅํ•˜์„ธ์š”:

  • NestJS ๊ณต์‹ Discord ์ฑ„๋„ ์ฐธ์—ฌ
  • Stack Overflow์—์„œ ์งˆ๋ฌธํ•˜๊ณ  ๋‹ต๋ณ€ํ•˜๊ธฐ
  • NestJS ๊ด€๋ จ ์ปจํผ๋Ÿฐ์Šค๋‚˜ ๋ฐ‹์—… ์ฐธ์„
  • ๊ธฐ์ˆ  ๋ธ”๋กœ๊ทธ ์šด์˜ํ•˜๊ธฐ

6.5 ๋งˆ์ง€๋ง‰ ์กฐ์–ธ

NestJS ๋งˆ์Šคํ„ฐ๊ฐ€ ๋˜๋Š” ๊ธธ์€ ๋Š์ž„์—†๋Š” ํ•™์Šต๊ณผ ์‹ค์ฒœ์˜ ๊ณผ์ •์ž…๋‹ˆ๋‹ค. ๊ธฐ์ˆ ์€ ๊ณ„์† ๋ฐœ์ „ํ•˜๊ณ  ์žˆ์œผ๋ฏ€๋กœ, ํ•ญ์ƒ ์ƒˆ๋กœ์šด ๊ฒƒ์„ ๋ฐฐ์šฐ๊ณ  ์ ์šฉํ•˜๋ ค๋Š” ์ž์„ธ๊ฐ€ ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

๐Ÿ’ก ์ตœ์ข… ํŒ: ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•  ๋•Œ๋งˆ๋‹ค "์ด ์ฝ”๋“œ๋ฅผ ์–ด๋–ป๊ฒŒ ๋” ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ์„๊นŒ?"๋ผ๊ณ  ์ž๋ฌธํ•ด ๋ณด์„ธ์š”. ์ด๋Ÿฌํ•œ ๋น„ํŒ์  ์‚ฌ๊ณ ๊ฐ€ ์—ฌ๋Ÿฌ๋ถ„์„ ๋” ๋‚˜์€ ๊ฐœ๋ฐœ์ž๋กœ ๋งŒ๋“ค์–ด ์ค„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์—ฌ๋Ÿฌ๋ถ„์˜ NestJS ์—ฌ์ •์ด ์—ฌ๊ธฐ์„œ ๋๋‚˜์ง€ ์•Š๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ ๋‹ค. ์ด์ œ ์—ฌ๋Ÿฌ๋ถ„์€ ์žฌ๋Šฅ๋„ท๊ณผ ๊ฐ™์€ ๋ณต์žกํ•œ ํ”Œ๋žซํผ์„ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ๋Š” ์ง€์‹๊ณผ ๋„๊ตฌ๋ฅผ ๊ฐ–์ถ”๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๋” ํฐ ๋„์ „์— ๋‚˜์„œ๊ณ , ํ˜์‹ ์ ์ธ ์†”๋ฃจ์…˜์„ ๋งŒ๋“ค์–ด๋‚ด๊ธธ ๋ฐ”๋ž๋‹ˆ๋‹ค. NestJS์˜ ์„ธ๊ณ„๋Š” ๊ด‘ํ™œํ•˜๊ณ  ๊ฐ€๋Šฅ์„ฑ์œผ๋กœ ๊ฐ€๋“ ์ฐจ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๋Ÿฌ๋ถ„์˜ ์ฐฝ์˜์„ฑ๊ณผ ์—ด์ •์œผ๋กœ ์ด ๊ฐ€๋Šฅ์„ฑ์„ ํ˜„์‹ค๋กœ ๋งŒ๋“ค์–ด ๋‚˜๊ฐ€์„ธ์š”!

๋งˆ์ง€๋ง‰์œผ๋กœ, ๊ฐœ๋ฐœ์€ ๋‹จ์ˆœํžˆ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ ์ด์ƒ์˜ ์˜๋ฏธ๋ฅผ ๊ฐ€์ง‘๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž์˜ ๋‹ˆ์ฆˆ๋ฅผ ์ดํ•ดํ•˜๊ณ , ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋ฉฐ, ๊ฐ€์น˜๋ฅผ ์ฐฝ์ถœํ•˜๋Š” ๊ณผ์ •์ž…๋‹ˆ๋‹ค. NestJS๋Š” ์ด๋Ÿฌํ•œ ๋ชฉํ‘œ๋ฅผ ๋‹ฌ์„ฑํ•˜๊ธฐ ์œ„ํ•œ ๊ฐ•๋ ฅํ•œ ๋„๊ตฌ์ผ ๋ฟ์ž…๋‹ˆ๋‹ค. ํ•ญ์ƒ ํฐ ๊ทธ๋ฆผ์„ ๋ณด๋ฉด์„œ ๊ฐœ๋ฐœ์— ์ž„ํ•˜์„ธ์š”.

์—ฌ๋Ÿฌ๋ถ„์˜ NestJS ๋งˆ์Šคํ„ฐ ์—ฌ์ •์— ํ–‰์šด์ด ํ•จ๊ป˜ํ•˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค. ๋Š์ž„์—†์ด ํ•™์Šตํ•˜๊ณ , ๋„์ „ํ•˜๊ณ , ์„ฑ์žฅํ•˜์„ธ์š”. ๋ฏธ๋ž˜์˜ ์ˆ™๋ จ๋œ NestJS ๊ฐœ๋ฐœ์ž์ธ ์—ฌ๋Ÿฌ๋ถ„์„ ์‘์›ํ•ฉ๋‹ˆ๋‹ค! ๐Ÿš€๐ŸŒŸ

7. ๋ถ€๋ก: ์œ ์šฉํ•œ ๋ฆฌ์†Œ์Šค ๋ฐ ๋„๊ตฌ ๐Ÿ“š๐Ÿ› ๏ธ

NestJS ํ•™์Šต๊ณผ ๊ฐœ๋ฐœ์„ ๋”์šฑ ํšจ๊ณผ์ ์œผ๋กœ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ช‡ ๊ฐ€์ง€ ์œ ์šฉํ•œ ๋ฆฌ์†Œ์Šค์™€ ๋„๊ตฌ๋ฅผ ์†Œ๊ฐœํ•ฉ๋‹ˆ๋‹ค. ์ด๋“ค์„ ํ™œ์šฉํ•˜์—ฌ ์—ฌ๋Ÿฌ๋ถ„์˜ NestJS ์Šคํ‚ฌ์„ ํ•œ ๋‹จ๊ณ„ ๋” ๋ฐœ์ „์‹œ์ผœ ๋ณด์„ธ์š”!

7.1 ๊ณต์‹ ๋ฌธ์„œ ๋ฐ ํ•™์Šต ์ž๋ฃŒ

7.2 ๊ฐœ๋ฐœ ๋„๊ตฌ

  • WebStorm - NestJS ๊ฐœ๋ฐœ์— ์ตœ์ ํ™”๋œ IDE
  • NestJS Snippets for VS Code - ์ฝ”๋“œ ์ž‘์„ฑ์„ ๋•๋Š” VS Code ํ™•์žฅ ํ”„๋กœ๊ทธ๋žจ
  • Testing NestJS - NestJS ํ…Œ์ŠคํŒ…์„ ์œ„ํ•œ ์œ ์šฉํ•œ ๋„๊ตฌ ๋ชจ์Œ

7.3 ์ปค๋ฎค๋‹ˆํ‹ฐ ๋ฐ ํฌ๋Ÿผ

  • NestJS Discord - ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋„์›€์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” ๊ณต์‹ ์ปค๋ฎค๋‹ˆํ‹ฐ
  • Stack Overflow NestJS ํƒœ๊ทธ - ๋ฌธ์ œ ํ•ด๊ฒฐ์— ๋„์›€์ด ๋˜๋Š” Q&A
  • GitHub Discussions - NestJS ๊ด€๋ จ ํ† ๋ก  ๋ฐ ์•„์ด๋””์–ด ๊ณต์œ 

7.4 ๋ธ”๋กœ๊ทธ ๋ฐ ๋‰ด์Šค๋ ˆํ„ฐ

  • Trilon Blog - NestJS ์ฝ”์–ด ํŒ€์˜ ์ธ์‚ฌ์ดํŠธ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋Š” ๋ธ”๋กœ๊ทธ
  • Dev.to NestJS ํƒœ๊ทธ - ์ปค๋ฎค๋‹ˆํ‹ฐ ๋ฉค๋ฒ„๋“ค์˜ ๋‹ค์–‘ํ•œ NestJS ๊ด€๋ จ ๊ธ€
  • NestJS ๋‰ด์Šค๋ ˆํ„ฐ - ์ตœ์‹  ์—…๋ฐ์ดํŠธ์™€ ํŒ์„ ๋ฐ›์•„๋ณผ ์ˆ˜ ์žˆ๋Š” ๊ณต์‹ ๋‰ด์Šค๋ ˆํ„ฐ

7.5 ์œ ์šฉํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ฐ ๋ชจ๋“ˆ

  • @nestjsx/crud - CRUD ์ž‘์—…์„ ๊ฐ„์†Œํ™”ํ•ด์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
  • @nestjs/terminus - ํ—ฌ์Šค์ฒดํฌ ๋ชจ๋“ˆ
  • @nestjs/config - ํ™˜๊ฒฝ ์„ค์ • ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ๋ชจ๋“ˆ
  • @ogma/nestjs-module - ๋กœ๊น…์„ ์œ„ํ•œ ๊ฐ•๋ ฅํ•œ ๋ชจ๋“ˆ

๐Ÿ’ก ํ•™์Šต ํŒ: ์ด๋Ÿฌํ•œ ๋ฆฌ์†Œ์Šค๋“ค์„ ์ •๊ธฐ์ ์œผ๋กœ ํ™•์ธํ•˜๊ณ  ํ™œ์šฉํ•˜์„ธ์š”. ๊ธฐ์ˆ  ํŠธ๋ Œ๋“œ์™€ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค๋Š” ๊ณ„์† ๋ณ€ํ™”ํ•˜๋ฏ€๋กœ, ์ง€์†์ ์ธ ํ•™์Šต์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

7.6 ์ฑ… ์ถ”์ฒœ

  • "NestJS in Action" by Kamil Mysliwiec (NestJS ์ฐฝ์‹œ์ž)
  • "Building Node.js APIs with NestJS" by Zachary Harris
  • "Hands-On RESTful Web Services with TypeScript 3" by Biharck Muniz Araรบjo

7.7 ์˜จ๋ผ์ธ ์ฝ”์Šค

์ด๋Ÿฌํ•œ ๋ฆฌ์†Œ์Šค๋“ค์„ ํ™œ์šฉํ•˜์—ฌ ์—ฌ๋Ÿฌ๋ถ„์˜ NestJS ์Šคํ‚ฌ์„ ์ง€์†์ ์œผ๋กœ ํ–ฅ์ƒ์‹œํ‚ค์„ธ์š”. ๊ธฐ์ˆ ์  ์—ญ๋Ÿ‰๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋ฌธ์ œ ํ•ด๊ฒฐ ๋Šฅ๋ ฅ, ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„ ๋Šฅ๋ ฅ๋„ ํ•จ๊ป˜ ๋ฐœ์ „์‹œ์ผœ ๋‚˜๊ฐ€๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค. NestJS ๋งˆ์Šคํ„ฐ๋กœ ๊ฐ€๋Š” ์—ฌ์ •์—์„œ ์ด ๋ฆฌ์†Œ์Šค๋“ค์ด ๋“ ๋“ ํ•œ ๊ธธ์žก์ด๊ฐ€ ๋˜์–ด์ค„ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

๐ŸŒŸ ์ตœ์ข… ์กฐ์–ธ: ๋‹จ์ˆœํžˆ ์†Œ๋น„ํ•˜๋Š” ๊ฒƒ์— ๊ทธ์น˜์ง€ ๋ง๊ณ , ๋ฐฐ์šด ๋‚ด์šฉ์„ ๋ฐ”ํƒ•์œผ๋กœ ์ง์ ‘ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์„ธ์š”. ์‹ค์ œ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๊ณผ์ •์—์„œ ๊ฐ€์žฅ ํฐ ์„ฑ์žฅ์ด ์ด๋ฃจ์–ด์ง‘๋‹ˆ๋‹ค. ์žฌ๋Šฅ๋„ท๊ณผ ๊ฐ™์€ ํ”„๋กœ์ ํŠธ๋ฅผ ๋‹จ๊ณ„์ ์œผ๋กœ ๊ตฌํ˜„ํ•ด๋ณด๋ฉด์„œ ์‹ค๋ ฅ์„ ํ‚ค์›Œ๋‚˜๊ฐ€์„ธ์š”!

NestJS ๋งˆ์Šคํ„ฐ๊ฐ€ ๋˜๋Š” ์—ฌ์ •์—์„œ ์ด ๊ฐ€์ด๋“œ์™€ ๋ฆฌ์†Œ์Šค๋“ค์ด ์—ฌ๋Ÿฌ๋ถ„์—๊ฒŒ ๋„์›€์ด ๋˜๊ธฐ๋ฅผ ๋ฐ”๋ž๋‹ˆ๋‹ค. ๋Š์ž„์—†๋Š” ํ˜ธ๊ธฐ์‹ฌ๊ณผ ํ•™์Šต ์—ด์ •์œผ๋กœ ์—ฌ๋Ÿฌ๋ถ„์˜ ๊ฐœ๋ฐœ ์Šคํ‚ฌ์„ ํ•œ ๋‹จ๊ณ„ ๋” ๋ฐœ์ „์‹œ์ผœ ๋‚˜๊ฐ€์„ธ์š”. ํ›Œ๋ฅญํ•œ NestJS ๊ฐœ๋ฐœ์ž๋กœ ์„ฑ์žฅํ•  ์—ฌ๋Ÿฌ๋ถ„์˜ ๋ฏธ๋ž˜๋ฅผ ์‘์›ํ•ฉ๋‹ˆ๋‹ค! ํ™”์ดํŒ…! ๐Ÿš€๐ŸŒŸ