๐Ÿ’ก 02. ๋ชจ๋“ˆ๊ณผ ์ปจํŠธ๋กค๋Ÿฌ โ€” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ผˆ๋Œ€์™€ ์ถœ์ž…๋ฌธ

2026๋…„ 2์›” 26์ผ ์ˆ˜์ •๋จ

๐Ÿ“‹ ๊ฐœ์š”

NestJS ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๋ผˆ๋Œ€์ธ ๋ชจ๋“ˆ๊ณผ, ํด๋ผ์ด์–ธํŠธ์˜ ์š”์ฒญ์„ ๋ฐ›์•„ ์‘๋‹ต์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ์˜ ๋ชจ๋“  ๊ฒƒ์„ ๋ฐฐ์›๋‹ˆ๋‹ค.

๐Ÿ“‹ ๋ชฉ์ฐจ


๐Ÿ“Œ ์ด ๋ฌธ์„œ๋ฅผ ์ฝ๊ธฐ ์ „์—

โฑ๏ธ ์˜ˆ์ƒ ์ฝ๊ธฐ ์‹œ๊ฐ„: 20๋ถ„ (์ „์ฒด) / ํ•ต์‹ฌ ํŒŒํŠธ๋งŒ: 10๋ถ„

๐Ÿงณ ์ „์ œ ์ง€์‹ ์ฒดํฌ๋ฆฌ์ŠคํŠธ
์•„๋ž˜ ํ•ญ๋ชฉ์„ ๋ชจ๋‘ ์•Œ๋ฉด ๋ฐ”๋กœ [๋ชจ๋“ˆ ์„น์…˜]๋ถ€ํ„ฐ ์ฝ์–ด๋„ ๋ผ:

  • ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ(@) ๋ฌธ๋ฒ•์„ ๋ณด๋ฉด ํ•จ์ˆ˜๋‚˜ ํด๋ž˜์Šค์— ์†์„ฑ์„ ๋ถ€์—ฌํ•˜๋Š” ๊ฒƒ์ž„์„ ์•ˆ๋‹ค.
  • HTTP ๋ฉ”์„œ๋“œ(GET, POST, PUT, DELETE)์˜ ์ฐจ์ด๋ฅผ ์•ˆ๋‹ค.
  • 01์žฅ 'NestJS ์†Œ๊ฐœ'์˜ ์›จ์ดํ„ฐ/์…ฐํ”„ ๋น„์œ ๋ฅผ ๊ธฐ์–ตํ•œ๋‹ค.

๐Ÿ—บ๏ธ ์ด ๋ฌธ์„œ์˜ ํ๋ฆ„
๋ชจ๋“ˆ๋กœ ์˜์—ญ ๋‚˜๋ˆ„๊ธฐ โ†’ ์ปจํŠธ๋กค๋Ÿฌ๋กœ ์š”์ฒญ ๋ฐ›๊ธฐ โ†’ ๋ฆฌํ€˜์ŠคํŠธ ๋ฐ์ดํ„ฐ(@Body, @Query) ๋ฝ‘์•„๋‚ด๊ธฐ โ†’ ๋ผ์šฐํŒ… ๊ทœ์น™ โ†’ ์—๋Ÿฌ ํ•ด๊ฒฐ ๋ฐ ํ€ด์ฆˆ

๐ŸŽฏ ์ด ๋ฌธ์„œ๋ฅผ ๋‹ค ์ฝ์œผ๋ฉด ํ•  ์ˆ˜ ์žˆ๋Š” ๊ฒƒ

  • ์šฉ๋„๋ณ„๋กœ ๋ชจ๋“ˆ์„ ์ชผ๊ฐœ์„œ(UsersModule, StocksModule) ์ •๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.
  • URL ์ฃผ์†Œ๋‚˜ ๋ณธ๋ฌธ์— ๋‹ด๊ธด ๋ฐ์ดํ„ฐ๋ฅผ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์ •ํ™•ํžˆ ๋นผ๋‚ผ ์ˆ˜ ์žˆ๋‹ค.
  • ๋ผ์šฐํŒ… ์ˆœ์„œ ์˜ค๋ฅ˜๋กœ ์ธํ•œ 404 ์—๋Ÿฌ ์›์ธ์„ ์Šค์Šค๋กœ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค.

๐Ÿค” ์™œ ์•Œ์•„์•ผ ํ•˜๋Š”๊ฐ€

๊ฑด๋ฌผ์„ ์ง€์„ ๋•Œ ์„ค๊ณ„๋„ ์—†์ด ๋ฌด์ž‘์ • ๋ฒฝ๋Œ๋งŒ ์Œ“์œผ๋ฉด, ๋‚˜์ค‘์— ํ™”์žฅ์‹ค ๋ฐฐ๊ด€์„ ๊ณ ์น˜๋ ค๊ณ  ๊ฑด๋ฌผ ์ „์ฒด๋ฅผ ๋ถ€์ˆด์•ผ ํ•ด.
NestJS์—์„œ ๋ชจ๋“ˆ ์€ ๊ฑด๋ฌผ์„ '์ธต(Floor)'๊ณผ '๊ตฌ์—ญ(Zone)'์œผ๋กœ ๋‚˜๋ˆ„๋Š” ์„ค๊ณ„๋„ ์—ญํ• ์„ ํ•˜๊ณ , ์ปจํŠธ๋กค๋Ÿฌ ๋Š” ๊ฐ ๊ตฌ์—ญ์— ๋“ค์–ด๊ฐ€๋Š” ์ถœ์ž…๋ฌธ ์—ญํ• ์„ ํ•ด.
์ด ๋‘ ๊ฐ€์ง€๋ฅผ ์ œ๋Œ€๋กœ ์•Œ๋ฉด, ์ฝ”๋“œ๊ฐ€ ์ˆ˜์ฒœ ์ค„๋กœ ๋Š˜์–ด๋‚˜๋„ ์–ด๋””๋ฅผ ๊ณ ์ณ์•ผ ํ• ์ง€ ๋‹จ 3์ดˆ ๋งŒ์— ์ฐพ์„ ์ˆ˜ ์žˆ๊ฒŒ ๋ผ.


๐Ÿ—๏ธ ๋น„์œ ๋กœ ๋จผ์ € ์ดํ•ดํ•˜๊ธฐ

๐Ÿง’ 5์‚ด์—๊ฒŒ ์„ค๋ช…ํ•œ๋‹ค๋ฉด?

์ปค๋‹ค๋ž€ ์ข…ํ•ฉ๋ณ‘์›์„ ์ƒ์ƒํ•ด๋ด.
๋ชจ๋“ˆ(Module) ์€ '์†Œ์•„๊ณผ', '๋‚ด๊ณผ', '์ •ํ˜•์™ธ๊ณผ' ๊ฐ™์€ ์ง„๋ฃŒ๊ณผ(๋ถ€์„œ)์•ผ. ์„œ๋กœ ํ•˜๋Š” ์ผ์ด ์™„๋ฒฝํ•˜๊ฒŒ ๋‚˜๋‰˜์–ด ์žˆ์ง€.
์ปจํŠธ๋กค๋Ÿฌ(Controller) ๋Š” ๊ฐ ์ง„๋ฃŒ๊ณผ ์•ž์— ์žˆ๋Š” '์•ˆ๋‚ด๋ฐ์Šคํฌ'์•ผ. "๋ฐฐ๊ฐ€ ์•„ํŒŒ์„œ ์™”์–ด์š”(์š”์ฒญ)" ํ•˜๋ฉด "๋‚ด๊ณผ 3๋ฒˆ ๋ฐฉ์œผ๋กœ ๊ฐ€์„ธ์š”" ํ•˜๊ณ  ๊ธธ์„ ์•ˆ๋‚ดํ•ด ์ฃผ์ง€. (์•ˆ๋‚ด๋ฐ์Šคํฌ ์ง์›์ด ์ง์ ‘ ๋ฐฐ๋ฅผ ์ˆ˜์ˆ ํ•˜์ง€ ์•Š์•„!)


๐Ÿงฉ ๋ชจ๋“ˆ (Module) โ€” ๊ฑด๋ฌผ์˜ ์ธต๊ณผ ๋ถ€์„œ ๐ŸŸก

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • ๋ชจ๋“ˆ์˜ 4๊ฐ€์ง€ ํ•ต์‹ฌ ๋ฐฐ์—ด(imports, controllers, providers, exports)์„ ๊ตฌ๋ณ„ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์™œ ํ•˜๋‚˜์˜ AppModule์— ๋‹ค ๋•Œ๋ ค๋ฐ•์œผ๋ฉด ์•ˆ ๋˜๋Š”์ง€ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค.

๐Ÿค” ์ž ๊น, ๋จผ์ € ์ƒ๊ฐํ•ด๋ด
์œ ์ € ๊ด€๋ฆฌ ๊ธฐ๋Šฅ, ์ฃผ์‹ ๊ฒ€์ƒ‰ ๊ธฐ๋Šฅ, ๊ฒฐ์ œ ๊ธฐ๋Šฅ์ด ๋ชจ๋‘ ํ•œ ํŒŒ์ผ์— ์„ž์—ฌ ์žˆ๋‹ค๊ณ  ์ƒ์ƒํ•ด๋ด. ๊ฒฐ์ œ ๊ธฐ๋Šฅ์„ ์ˆ˜์ •ํ•˜๋‹ค๊ฐ€ ์œ ์ € ๊ธฐ๋Šฅ์ด ๊ณ ์žฅ๋‚˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?

์ด๊ฒŒ ๋ญ”๊ฐ€?

์—ฐ๊ด€๋œ ์ฝ”๋“œ(์›จ์ดํ„ฐ์™€ ์…ฐํ”„)๋ฅผ ํ•˜๋‚˜์˜ ์šธํƒ€๋ฆฌ๋กœ ๋ฌถ์–ด์ฃผ๋Š” ๋‹จ์œ„์•ผ.

import { Module } from '@nestjs/common';
import { StocksController } from './stocks.controller';
import { StocksService } from './stocks.service';
import { DatabaseModule } from '../database/database.module';
 
@Module({
  imports: [DatabaseModule],       // 1. ๋‚จ์˜ ๋ถ€์„œ(๋ชจ๋“ˆ)์—์„œ ๋นŒ๋ ค์˜ฌ ๊ฒƒ
  controllers: [StocksController], // 2. ์šฐ๋ฆฌ ๋ถ€์„œ์˜ ์›จ์ดํ„ฐ(์ ‘์ˆ˜์ฒ˜)
  providers: [StocksService],      // 3. ์šฐ๋ฆฌ ๋ถ€์„œ์˜ ์…ฐํ”„(์‹ค์ œ ๋กœ์ง)
  exports: [StocksService],        // 4. ๋‚จ์˜ ๋ถ€์„œ์— ๋นŒ๋ ค์ค„ ๊ฒƒ
})
export class StocksModule {}

์™œ ์ด๊ฒŒ ๋”ฐ๋กœ ์กด์žฌํ•˜๋Š”๊ฐ€?

โŒ ์ด๊ฒŒ ์—†์—ˆ๋‹ค๋ฉด?
๋ชจ๋“  ์ฝ”๋“œ๊ฐ€ AppModule ํ•˜๋‚˜์— ๋‹ค ๋ชฐ๋ ค์žˆ์„ ๊ฑฐ์•ผ. 100๋ช…์˜ ์›จ์ดํ„ฐ์™€ 100๋ช…์˜ ์…ฐํ”„๊ฐ€ ๋ฒฝ๋„ ์—†๋Š” ๊ฑฐ๋Œ€ํ•œ ๊ฐ•๋‹น ํ•˜๋‚˜์—์„œ ์ผํ•œ๋‹ค๊ณ  ์ƒ๊ฐํ•ด๋ด. ๋ˆ„๊ฐ€ ๋ˆ„๊ตฌ๋ž‘ ์ผํ•˜๋Š”์ง€ ์•„๋ฌด๋„ ๋ชจ๋ฅผ ๊ฑฐ์•ผ (๊ฐ•ํ•œ ๊ฒฐํ•ฉ๋„).

โœ… ์ด๊ฒŒ ์ƒ๊ธด ๋’ค์—๋Š”?
"์ฃผ์‹ ๋ถ€์„œ(StocksModule)", "์œ ์ € ๋ถ€์„œ(UsersModule)"๊ฐ€ ๋…๋ฆฝ๋œ ์‚ฌ๋ฌด์‹ค์„ ๊ฐ€์ ธ. ์ฃผ์‹ ๋ถ€์„œ๋ฅผ ๋‹ค๋ฅธ ํ”„๋กœ์ ํŠธ์— ๊ทธ๋Œ€๋กœ ๋ณต์‚ฌํ•ด์„œ ์จ๋„ ์™„๋ฒฝํ•˜๊ฒŒ ๋™์ž‘ํ•ด (์žฌ์‚ฌ์šฉ์„ฑ/์บก์Аํ™”).

๋ชจ๋“ˆ ์„ค์ • 4๋Œ€์žฅ ๋œฏ์–ด๋ณด๊ธฐ

ํŒŒ๋ผ๋ฏธํ„ฐ๋น„์œ ์„ค๋ช…
imports์™ธ๋ถ€ ์šฉ์—ญ ๊ณ„์•ฝ์šฐ๋ฆฌ ๋ถ€์„œ๊ฐ€ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ ์œ„ํ•ด ํ•„์š”ํ•œ ๋‹ค๋ก  ๋ชจ๋“ˆ (์˜ˆ: DB ๋ชจ๋“ˆ)
controllers์šฐ๋ฆฌ ๋ถ€์„œ ์ ‘์ˆ˜์ฒ˜ํด๋ผ์ด์–ธํŠธ์˜ HTTP ์š”์ฒญ์„ ๋ฐ›์„ ํด๋ž˜์Šค ๋ชฉ๋ก
providers์šฐ๋ฆฌ ๋ถ€์„œ ์ง์›๋“ค์‹ค์ œ ์ผ์„ ํ•˜๋Š” ์„œ๋น„์Šค๋‚˜ ์ €์žฅ์†Œ. NestJS๊ฐ€ ์ธ์Šคํ„ด์Šค๋ฅผ ๊ด€๋ฆฌํ•ด์คŒ
exports์™ธ๋ถ€ ์„œ๋น„์Šค ๊ฐœ๋ฐฉ์šฐ๋ฆฌ ๋ถ€์„œ ์ง์› ์ค‘, ๋‹ค๋ฅธ ๋ถ€์„œ๊ฐ€ ๋ถ€๋ฅผ ์ˆ˜ ์žˆ๊ฒŒ ํ—ˆ๋ฝํ•ด์ค„ ์ง์› ๋ชฉ๋ก

๐Ÿ”— ์—ฐ๊ฒฐ ๊ณ ๋ฆฌ
๊ณต์œ  ๋ชจ๋“ˆ์ด๋‚˜ ๊ธ€๋กœ๋ฒŒ ๋ชจ๋“ˆ(@Global()) ์„ค์ •์€ ๋‚˜์ค‘์— ์•„ํ‚คํ…์ฒ˜๋ฅผ ๊นŠ๊ฒŒ ์งค ๋•Œ ๋‹ค์‹œ ๋‚˜์™€. ์ผ๋‹จ์€ "๊ด€๋ จ๋œ ๊ฒƒ๋ผ๋ฆฌ ๋ฌถ๋Š”๋‹ค"๋งŒ ๊ธฐ์–ตํ•ด!


๐Ÿงฉ ์ปจํŠธ๋กค๋Ÿฌ (Controller) โ€” ์•ˆ๋‚ด๋ฐ์Šคํฌ ์ง์› ๐ŸŸข

๐ŸŽฏ ์ด ์„น์…˜์„ ์ฝ๊ณ  ๋‚˜๋ฉด:

  • HTTP ์š”์ฒญ์—์„œ ํŒŒ๋ผ๋ฏธํ„ฐ(@Query, @Param, @Body)๋ฅผ ๋ฝ‘์•„๋‚ด๋Š” ๋ฐฉ๋ฒ•์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
  • @Controller ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋ฅผ ์ด์šฉํ•ด ๊ฒฝ๋กœ๋ฅผ ๊น”๋”ํ•˜๊ฒŒ ์„ค๊ณ„ํ•  ์ˆ˜ ์žˆ๋‹ค.

1. ๋ผ์šฐํŒ…๊ณผ ๋ฉ”์„œ๋“œ ๋งคํ•‘

์ปจํŠธ๋กค๋Ÿฌ๋Š” URL ์ฃผ์†Œ์™€ HTTP ๋ฉ”์„œ๋“œ๋ฅผ ํ™•์ธํ•˜๊ณ , ์ ์ ˆํ•œ ์ฒ˜๋ฆฌ ํ•จ์ˆ˜๋กœ ์—ฐ๊ฒฐํ•ด์ค˜.

import { Controller, Get, Post, Param, Body, HttpCode, HttpStatus } from '@nestjs/common';
 
@Controller('stocks')  // ๐Ÿ‘ˆ ์ด ์ปจํŠธ๋กค๋Ÿฌ๋Š” '/stocks'๋กœ ์‹œ์ž‘ํ•˜๋Š” ์š”์ฒญ๋งŒ ๋‹ด๋‹นํ•ด!
export class StocksController {
  
  // GET /stocks
  @Get()
  findAll() {
    return '๋ชจ๋“  ์ฃผ์‹ ๋ชฉ๋ก์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.';
  }
 
  // GET /stocks/AAPL
  @Get(':symbol') 
  findOne(@Param('symbol') symbol: string) {
    // URL์— ์žˆ๋Š” 'AAPL' ๊ธ€์ž๋ฅผ symbol ๋ณ€์ˆ˜๋กœ ์™ ๋นผ์˜ด!
    return `${symbol} ์ฃผ์‹์˜ ์ •๋ณด์ž…๋‹ˆ๋‹ค.`;
  }
 
  // POST /stocks
  @Post()
  @HttpCode(HttpStatus.CREATED) // ์„ฑ๊ณตํ•˜๋ฉด 201 ์ฝ”๋“œ๋ฅผ ์ด์คŒ
  create(@Body() dto: CreateStockDto) {
    // ์š”์ฒญ ๋ณธ๋ฌธ(JSON) ์ „์ฒด๋ฅผ dto๋ผ๋Š” ๊ฐ์ฒด๋กœ ๋ฐ›์•„์˜ด!
    return '์ฃผ์‹์ด ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.';
  }
}

2. ์š”์ฒญ ๋ฐ์ดํ„ฐ ์ถ”์ถœ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์ด์ •๋ฆฌ

๊ณ ๊ฐ(ํด๋ผ์ด์–ธํŠธ)์ด ์•ˆ๋‚ด๋ฐ์Šคํฌ์— ๋„˜๊ฒจ์ฃผ๋Š” ์ •๋ณด์˜ ํ˜•ํƒœ๋Š” ์—ฌ๋Ÿฌ ๊ฐ€์ง€์•ผ. ์ฃผ์†Œ์ฐฝ์— ์ ์–ด์„œ ์ค„ ์ˆ˜๋„ ์žˆ๊ณ , ๋ด‰ํˆฌ์— ๋‹ด์•„์„œ ์ค„ ์ˆ˜๋„ ์žˆ์ง€.

๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์–ด๋””์„œ ๋ฝ‘์•„์˜ค๋‚˜?์˜ˆ์‹œ URL / ์ƒํ™ฉ
@Param('id')URL ๊ฒฝ๋กœ์˜ ๋ณ€์ˆ˜ ์ž๋ฆฌGET /users/123 โ†’ id = 123
@Query('page')URL ๋ฌผ์Œํ‘œ(?) ๋’คGET /users?page=2 โ†’ page = 2
@Body()์š”์ฒญ ๋ณธ๋ฌธ (JSON ๋ฉ์–ด๋ฆฌ)๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ(POST), ์ˆ˜์ •(PUT) ์‹œ ๋ฐ์ดํ„ฐ
@Headers('auth')์ˆจ๊ฒจ์ง„ ๋ฉ”ํƒ€ ์ •๋ณด์ธ์ฆ ํ† ํฐ์„ ํ™•์ธํ•  ๋•Œ

3. ์‘๋‹ต(Response) ์ฒ˜๋ฆฌ ๋ฐฉ์‹

Express์—์„œ๋Š” res.status(200).json(...) ์ฒ˜๋Ÿผ ์ผ์ผ์ด ์‘๋‹ต์„ ๋ณด๋ƒˆ์ง€๋งŒ, NestJS๋Š” ํ›จ์”ฌ ๋˜‘๋˜‘ํ•ด.
์ปจํŠธ๋กค๋Ÿฌ ํ•จ์ˆ˜์—์„œ ๊ทธ๋ƒฅ return ๊ฐ์ฒด๋งŒ ๋˜์ง€๋ฉด:

  1. ์•Œ์•„์„œ JSON ๋ฌธ์ž์—ด๋กœ ๋ฐ”๊ฟ”์คŒ!
  2. ์•Œ์•„์„œ ์ƒํƒœ ์ฝ”๋“œ 200(์„ฑ๊ณต)์„ ๋‹ฌ์•„์คŒ! (POST๋Š” 201)

๐Ÿ’ก ํ•œ ์ค„๋กœ ๊ธฐ์–ตํ•˜๊ธฐ

์ปจํŠธ๋กค๋Ÿฌ๋Š” ๊ธธ๋ผ์žก์ด์•ผ. ์–ด๋–ค URL๋กœ ๋“ค์–ด์˜จ ์–ด๋–ค ๋ฐ์ดํ„ฐ์ธ์ง€๋งŒ ๋ฝ‘์•„์„œ ์„œ๋น„์Šค(์…ฐํ”„)์—๊ฒŒ ๋˜์ ธ์ฃผ๊ณ  ๋’ท์ผ์€ ์‹ ๊ฒฝ ์“ฐ์ง€ ๋งˆ.


๐Ÿงช ๋”ฐ๋ผํ•ด๋ณด๊ธฐ: ์‹ค์ „ ์ฃผ์‹ ์ปจํŠธ๋กค๋Ÿฌ ๋งŒ๋“ค๊ธฐ

๋‹จ์ˆœํ•œ ์˜ˆ์‹œ๋ฅผ ๋„˜์–ด, ์‹ค์ œ ์‹ค๋ฌด์—์„œ ์“ธ ๋ฒ•ํ•œ ์ฃผ์‹ ๊ฒ€์ƒ‰์šฉ ๋ณตํ•ฉ ํŒŒ๋ผ๋ฏธํ„ฐ ์ปจํŠธ๋กค๋Ÿฌ๋ฅผ ๋งŒ๋“ค์–ด๋ณด์ž.

๐Ÿ‘‡ ์•„๋ž˜ ์ฝ”๋“œ๋ฅผ ๋ˆˆ์œผ๋กœ ์ญ‰ ๋”ฐ๋ผ๊ฐ€๋ฉฐ ์ฝ์–ด๋ด.

@Get('search')
// ์ฃผ์†Œ: GET /stocks/search?exchange=NASDAQ&sector=Tech&page=1
search(
  @Query('exchange') exchange?: string,
  @Query('sector') sector?: string,
  @Query('page', ParseIntPipe) page: number = 1,
  @Query('limit', ParseIntPipe) limit: number = 20,
) {
  // 1. ํ•„์š”ํ•œ ์š”์†Œ๋ฅผ ๋‹ค ๋ฝ‘์•˜์œผ๋‹ˆ ์…ฐํ”„(์„œ๋น„์Šค)์—๊ฒŒ ์š”๋ฆฌ ์˜๋ขฐ!
  return this.stocksService.search(exchange, sector, page, limit);
}

๐Ÿ” ์ฝ”๋“œ ํ•œ ์ค„์”ฉ ๋œฏ์–ด๋ณด๊ธฐ:

๋ถ€๋ถ„์˜๋ฏธ
@Get('search')์ด ํ•จ์ˆ˜์˜ ๋ผ์šฐํŠธ๋Š” ๋ถ€๋ชจ์˜ /stocks ์™€ ํ•ฉ์ณ์ ธ์„œ /stocks/search๊ฐ€ ๋ผ.
exchange?: string? ๊ธฐํ˜ธ๋Š” "์ด ๊ฐ’์ด ์•ˆ ๋“ค์–ด์˜ฌ ์ˆ˜๋„ ์žˆ๋‹ค(์˜ต์…˜)"๋Š” ๋œป์ด์•ผ.
ParseIntPipe์ฟผ๋ฆฌ์ŠคํŠธ๋ง์€ ์›๋ž˜ ๋ฌด์กฐ๊ฑด ๋ฌธ์ž์—ด("1")์ธ๋ฐ, ์ด๊ฑธ ์ง„์งœ ์ˆซ์ž(1)๋กœ ์•Œ์•„์„œ ๋ฐ”๊ฟ”์ฃผ๊ณ  ์ˆซ์ž๊ฐ€ ์•„๋‹ˆ๋ฉด 400 ์—๋Ÿฌ๋ฅผ ๋ฑ‰์–ด๋‚ด๋Š” ๋˜‘๋˜‘ํ•œ ๋ฌธ์ง€๊ธฐ์•ผ.
= 1ํด๋ผ์ด์–ธํŠธ๊ฐ€ page ๊ฐ’์„ ์•ˆ ๋„˜๊ฒจ์ฃผ๋ฉด ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ 1์„ ์“ฐ๊ฒ ๋‹ค๋Š” ๋œป์ด์•ผ.

โœ… ์‹ค์Šต ํ›„ ์ฒดํฌ๋ฆฌ์ŠคํŠธ

  • @Param๊ณผ @Query๊ฐ€ ๊ฐ๊ฐ URL์˜ ์–ด๋А ๋ถ€๋ถ„์—์„œ ๊ฐ’์„ ๋ฝ‘์•„์˜ค๋Š”์ง€ ๊ตฌ๋ณ„ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ParseIntPipe๊ฐ€ ์™œ ํ•„์š”ํ•œ์ง€ ์„ค๋ช…ํ•  ์ˆ˜ ์žˆ๋‹ค.

์•„์ง ์•„๋ฆฌ์†กํ•˜๋‹ค๋ฉด [์š”์ฒญ ๋ฐ์ดํ„ฐ ์ถ”์ถœ ์ด์ •๋ฆฌ ํ‘œ]๋ฅผ ๋‹ค์‹œ ํ•œ๋ฒˆ ์Šค์œฝ ํ›‘๊ณ  ์˜ค์ž!


๐Ÿ’ผ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค์™€ ์‹ค๋ฌด ํŒ ๐ŸŸก

1. ๋ผ์šฐํŠธ ์ˆœ์„œ ์ฃผ์˜ (์น˜๋ช…์  ์‹ค์ˆ˜ ๋ฐฉ์ง€)

REST API๋ฅผ ์งค ๋•Œ ์ œ์ผ ๋งŽ์ด ๋‹นํ•˜๋Š” ํ•จ์ • ์ค‘ ํ•˜๋‚˜์•ผ. ๊ตฌ์ฒด์ ์ธ ๊ฒฝ๋กœ๋ฅผ ๋™์  ๋งค๊ฐœ๋ณ€์ˆ˜(:id)๋ณด๋‹ค ๋ฌด์กฐ๊ฑด ๋จผ์ € ์จ์•ผ ํ•ด.

// โœ… ์ข‹์€ ์˜ˆ: ๊ตฌ์ฒด์ ์ธ 'trending'์ด ๋จผ์ € ๋‚˜์˜ด
@Get('trending')
getTrending() { return '์ธ๊ธฐ ์ฃผ์‹'; }
 
@Get(':symbol')
getBySymbol(@Param('symbol') symbol: string) { return symbol; }
 
 
// โŒ ๋‚˜์œ ์—ญ์ˆœ ์˜ˆ:
@Get(':symbol') // ๋ชจ๋“  ๊ฒƒ์„ ๋‹ค ๋นจ์•„๋“ค์ž„
getBySymbol() { ... }
 
@Get('trending') // ์ด ์ฝ”๋“œ๋Š” ํ‰์ƒ ์‹คํ–‰๋˜์ง€ ๋ชปํ•จ (์œ„์— ๋ง‰ํ˜€์„œ)
getTrending() { ... }

2. ์ปจํŠธ๋กค๋Ÿฌ๋Š” "์ข…์ž‡์žฅ์ฒ˜๋Ÿผ ์–‡๊ฒŒ" ์œ ์ง€ํ•ด๋ผ

์›จ์ดํ„ฐ๊ฐ€ ์ฃผ๋ฐฉ์— ๋“ค์–ด๊ฐ€์„œ ๊ณ ๊ธฐ ๊ตฌ์šฐ๋ฉด ์•ˆ ๋ผ. ์ปจํŠธ๋กค๋Ÿฌ์— const user = await db.query... ๊ฐ™์€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋กœ์ง์ด 1์ค„์ด๋ผ๋„ ๋ณด์ธ๋‹ค๋ฉด ์„ค๊ณ„๊ฐ€ ๋ง๊ฐ€์ง„ ๊ฑฐ์•ผ. ์˜ค์ง ์œ ํšจ์„ฑ ์ฒดํฌ, ์˜๋ขฐ, ์‘๋‹ต๋งŒ ๋‹ด๋‹นํ•ด!

3. API ๊ธ€๋กœ๋ฒŒ ์ ‘๋‘์‚ฌ (Global Prefix) ์ถ”๊ฐ€

์‹ค๋ฌด์—์„œ๋Š” API ์ฃผ์†Œ ์•ž์— /api/v1 ๊ฐ™์€ ๊ฑธ ๋ถ™์—ฌ. ํด๋ผ์ด์–ธํŠธ์™€ ๋ฆฌ์•กํŠธ ์•ฑ ๊ฒฝ๋กœ๊ฐ€ ๊ฒน์น˜์ง€ ์•Š๊ฒŒ ํ•˜๊ธฐ ์œ„ํ•ด์„œ์•ผ.
main.ts ์ˆ˜์ • ํ•œ ์ค„์ด๋ฉด ์•ฑ ์ „์ฒด์— ์ ์šฉ๋ผ:

// main.ts
app.setGlobalPrefix('api/v1');
// ์ด์ œ localhost:3000/api/v1/stocks ๋กœ ์š”์ฒญํ•ด์•ผ ํ•ด!

๐Ÿ’ฅ ์—๋Ÿฌ ํ•ด๊ฒฐ ์นดํƒˆ๋กœ๊ทธ

์—๋Ÿฌ ๋ฉ”์‹œ์ง€๊ฐ€ ๋œจ๋ฉด Ctrl+F๋กœ ๊ฒ€์ƒ‰ํ•ด๋ด.

โŒ 404 Not Found (๋ผ์šฐํŒ… ๋ฌธ์ œ)

์–ธ์ œ ๋‚˜์˜ค๋Š”๊ฐ€?

{ "statusCode": 404, "message": "Cannot GET /stocks/trending", "error": "Not Found" }

์›์ธ:

  1. ๋ชจ๋“ˆ์„ AppModule์˜ imports์— ๋“ฑ๋กํ•˜์ง€ ์•Š์•„์„œ NestJS๊ฐ€ ์ปจํŠธ๋กค๋Ÿฌ ์ž์ฒด๋ฅผ ๋ชจ๋ฆ„.
  2. ์œ„์—์„œ ์„ค๋ช…ํ•œ ๋ผ์šฐํŠธ ์ˆœ์„œ(์ˆœ์œ„) ๋ฌธ์ œ ๋•Œ๋ฌธ์—, /:symbol์ด /trending์„ ์‚ผ์ผœ๋ฒ„๋ ค์„œ ์—๋Ÿฌ ์ฒ˜๋ฆฌ๊ธฐ๋กœ ๋น ์ง.

ํ•ด๊ฒฐ์ฑ…:

  • app.module.ts์˜ imports: [StocksModule]์ด ๋“ค์–ด์žˆ๋Š”์ง€ ํ™•์ธ.
  • ์ปจํŠธ๋กค๋Ÿฌ ๋‚ด ํ•จ์ˆ˜ ๋ฐฐ์น˜ ์ˆœ์„œ์—์„œ ๊ณ ์ •๋œ ์˜๋‹จ์–ด ๊ฒฝ๋กœ๊ฐ€ ์ฝœ๋ก (:)์ด ๋ถ™์€ ๋ณ€์ˆ˜ ๊ฒฝ๋กœ๋ณด๋‹ค ์œ„์— ์žˆ๋Š”์ง€ ํ™•์ธ.

โŒ Validation failed (numeric string is expected)

์–ธ์ œ ๋‚˜์˜ค๋Š”๊ฐ€?

{ "statusCode": 400, "message": "Validation failed (numeric string is expected)", "error": "Bad Request" }

์›์ธ: @Query('id', ParseIntPipe)๋ฅผ ์ผ๋Š”๋ฐ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ?id=abc ์ฒ˜๋Ÿผ ๋ฌธ์ž๋ฅผ ๋ณด๋ƒˆ๋‹ค.

ํ•ด๊ฒฐ์ฑ…:
์ •์ƒ์ ์ธ ๋™์ž‘์ด๋‹ค. ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž์—๊ฒŒ "์ˆซ์ž๋ฅผ ๋ณด๋‚ด์„ธ์š”!" ๋ผ๊ณ  ์•Œ๋ ค์ฃผ๋ฉด ๋œ๋‹ค. ํŒŒ์ดํ”„๊ฐ€ ์„œ๋ฒ„ ๋‹ค์šด์„ ๋ง‰์•„์ค€ ๊ณ ๋งˆ์šด ์ƒํ™ฉ.


๐Ÿ—‚๏ธ ์น˜ํŠธ์‹œํŠธ โ€” ์‹ค๋ฌด ์š”์•ฝ ์นด๋“œ

์ด๊ฒƒ๋งŒ ๋ณต๋ถ™ํ•ด์„œ ์จ!

๐Ÿ“‹ ์š”์ฒญ ๋ฐ›๊ธฐ 3์ด์‚ฌ

๋‚ด๊ฐ€ ๋นผ์˜ค๊ณ  ์‹ถ์€ ์ •๋ณด์ด๋ ‡๊ฒŒ ์จ!
/users/123 ๐Ÿ‘‰ 123@Param('id') id: string
/search?kw=์•ˆ๋…• ๐Ÿ‘‰ ์•ˆ๋…•@Query('kw') kw: string
POST { "name": "Kim" }@Body() dto: UserDto

โš ๏ธ ์ ˆ๋Œ€ ํ•˜์ง€ ๋ง ๊ฒƒ

์ƒํ™ฉโŒ ๋‚˜์œ ์˜ˆ (๊ธˆ์ง€)โœ… ์ข‹์€ ์˜ˆ
์ปจํŠธ๋กค๋Ÿฌ์˜ ์—ญํ• ์ปจํŠธ๋กค๋Ÿฌ ํŒŒ์ผ์— DB SQL ์ฟผ๋ฆฌ ์ž‘์„ฑ์„œ๋น„์Šค ํ•จ์ˆ˜์— ํŒŒ๋ผ๋ฏธํ„ฐ๋งŒ ๋„˜๊ธฐ๊ธฐ
Express ๊ฐ์ฒด ์‚ฌ์šฉ@Res() res: Response ๋ฌด๋ถ„๋ณ„ํ•˜๊ฒŒ ์“ฐ๊ธฐํ”„๋ ˆ์ž„์›Œํฌ๊ฐ€ ์‘๋‹ตํ•˜๊ฒŒ ์ˆœ์ˆ˜ ๋ฐ˜ํ™˜

๐Ÿ“ ๋งˆ๋ฌด๋ฆฌ ํ€ด์ฆˆ

Q1. ๋‹น์‹ ์€ ๊ณ ๊ฐ์˜ ํ”„๋กœํ•„์„ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋ผ์šฐํ„ฐ๋ฅผ ๋งŒ๋“ค๊ณ  ์‹ถ๋‹ค. URL ๊ฒฝ๋กœ ๊ทœ์น™๊ณผ HTTP ๋ฉ”์„œ๋“œ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ ์กฐํ•ฉ์œผ๋กœ ์˜ฌ๋ฐ”๋ฅธ ๊ฒƒ์€?

  • A) @Post('/user/update')
  • B) @Put(':id')
  • C) @Get('/update/:id')
  • D) @Controller(':id')

โœ… ์ •๋‹ต: B

๐Ÿ’ก **์„ค๋ช…:**REST API ๊ทœ์•ฝ์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ์˜ ์—…๋ฐ์ดํŠธ(์ˆ˜์ •)๋Š” PUT ๋˜๋Š” PATCH๋ฅผ ์“ฐ๋ฉฐ, ์ˆ˜์ •ํ•  ๋Œ€์ƒ์˜ ์‹๋ณ„์ž๋Š” @Param์œผ๋กœ ๋„˜๊ธฐ๋Š” ๊ฒƒ์ด ์ •์„์ด์•ผ.

Q2. ์•„๋ž˜ ๋นˆ์นธ์„ ์ฑ„์›Œ๋ด.

์ปจํŠธ๋กค๋Ÿฌ์—์„œ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ๋ณด๋‚ธ JSON ๋ฐ์ดํ„ฐ(์˜ˆ: ํšŒ์›๊ฐ€์ž… ์–‘์‹) ์ „์ฒด๋ฅผ ํ•˜๋‚˜์˜ ๊ฐ์ฒด๋กœ ์ด์˜๊ฒŒ ๊ฐ€์ ธ์˜ค๊ณ  ์‹ถ์„ ๋•Œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š”?

@Post()
signup( [         ] body: SignupDto ) {
  return this.authService.register(body);
}

โœ… ์ •๋‹ต: @Body()
๐Ÿ“Œ ํ•ต์‹ฌ ๊ธฐ์–ต๋ฒ•: ํƒ๋ฐฐ ์ƒ์ž(HTTP ํ†ต์‹ ) ์•ˆ์— ๋“ค์–ด์žˆ๋Š” ๋‚ด์šฉ๋ฌผ(๋ณธ๋ฌธ = Body)!

Q3. ๋””๋ฒ„๊น… ํ€ด์ฆˆ: ์•„๋ž˜ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ๋ฐœ์ƒํ•  ๋ฌธ์ œ๋ฅผ ์ฐพ์•„๋ด.

@Controller('files')
export class FilesController {
  
  @Get(':filename')
  downloadFile(@Param('filename') name: string) {
    return `ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ: ${name}`;
  }
 
  @Get('download-logs')
  downloadAllLogs() {
    return '๋ชจ๋“  ๋กœ๊ทธ ์••์ถ• ๋‹ค์šด๋กœ๋“œ';
  }
}

์ด ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•˜๊ณ  /files/download-logs ์ฃผ์†Œ๋กœ ๋“ค์–ด๊ฐ€๋ฉด ์–ด๋–ค ์ผ์ด ์ƒ๊ธธ๊นŒ?

  • A) ์›ํ•˜๋˜ ๋Œ€๋กœ '๋ชจ๋“  ๋กœ๊ทธ ์••์ถ• ๋‹ค์šด๋กœ๋“œ'๊ฐ€ ๋‚˜์˜จ๋‹ค.
  • B) 'ํŒŒ์ผ ๋‹ค์šด๋กœ๋“œ: download-logs' ๋ผ๋Š” ๋ฌธ๊ตฌ๊ฐ€ ๋œฌ๋‹ค.
  • C) 500 ์—๋Ÿฌ ์„œ๋ฒ„ ํฌ๋ž˜์‹œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

โœ… ์ •๋‹ต: B

๐Ÿ’ก ์„ค๋ช…: ๊ฐ€์žฅ ์ง•๊ทธ๋Ÿฝ๊ฒŒ ๊ฒช์„ ๋ผ์šฐํŒ… ์ˆœ์„œ ์˜ค๋ฅ˜์•ผ! ๊ตฌ์ฒด์ ์ธ ๊ฒฝ๋กœ์ธ @Get('download-logs')๋ฅผ @Get(':filename') ๋ณด๋‹ค ์œ„๋กœ ์˜ฌ๋ ค์•ผ ํ•ด. ์•ˆ ๊ทธ๋Ÿฌ๋ฉด NestJS๋Š” "์•„, ๋‹ˆ๊ฐ€ ์š”์ฒญํ•œ ํŒŒ์ผ ์ด๋ฆ„์ด download-logs ๊ตฌ๋‚˜!" ํ•˜๊ณ  ์ฐฉ๊ฐํ•ด๋ฒ„๋ ค.


๐Ÿ”— ๋” ์•Œ์•„๋ณด๊ธฐ