Y J S

REST API ์„ค๊ณ„ ๊ฐ€์ด๋“œ

REST๋ž€ ๋ฌด์—‡์ธ๊ฐ€

  • ๋ฆฌ์†Œ์Šค๋ฅผ URI๋กœ ์‹๋ณ„ํ•˜๊ณ , ํ‘œํ˜„(์ฃผ๋กœ JSON)๊ณผ ํ‘œ์ค€ HTTP๋ฅผ ์ด์šฉํ•ด ์กฐ์ž‘ํ•˜๋Š” ์•„ํ‚คํ…์ฒ˜ ์Šคํƒ€์ผ.
  • ์ œ์•ฝ: Client-Server, Stateless, Cacheable, Uniform Interface, Layered System, Code-on-Demand(์„ ํƒ).

๋ฆฌ์†Œ์Šค ๋ชจ๋ธ๋ง๊ณผ URI ๊ทœ์น™

  • ๋ช…์‚ฌ+๋ณต์ˆ˜ํ˜•: /users, /users/{id}, /users/{id}/posts
  • ๊ด€๊ณ„ ํ‘œํ˜„์€ ํ•˜์œ„ ๋ฆฌ์†Œ์Šค๋กœ: /orders/{id}/items
  • ํ–‰์œ„๋Š” ๋ฉ”์„œ๋“œ๋กœ ํ‘œํ˜„ํ•˜๊ณ , URI์— ๋™์‚ฌ๋ฅผ ํ”ผํ•จ(์˜ˆ์™ธ: ๋น„์ฆˆ๋‹ˆ์Šค ์•ก์…˜์€ /payments/{id}:capture์ฒ˜๋Ÿผ ๋ช…์‹œ์  ์—”๋“œํฌ์ธํŠธ ๊ณ ๋ ค).

HTTP ๋ฉ”์„œ๋“œ์™€ ๋ฉฑ๋“ฑ์„ฑ

  • GET(์กฐํšŒ, ๋ฉฑ๋“ฑ), POST(์ƒ์„ฑ/์•ก์…˜), PUT(์ „์ฒด ์ˆ˜์ •, ๋ฉฑ๋“ฑ), PATCH(๋ถ€๋ถ„ ์ˆ˜์ •), DELETE(์‚ญ์ œ, ๋ฉฑ๋“ฑ)
  • ๋ฉฑ๋“ฑ์„ฑ: ๋™์ผ ์š”์ฒญ์„ ์—ฌ๋Ÿฌ ๋ฒˆ ๋ณด๋‚ด๋„ ์„œ๋ฒ„ ์ƒํƒœ๊ฐ€ ๋™์ผํ•˜๊ฒŒ ์œ ์ง€๋˜์–ด์•ผ ํ•จ(ๅ‰ฏ์ž‘์šฉ์€ ๋กœ๊ทธ ๋“ฑ ํ—ˆ์šฉ ๊ฐ€๋Šฅํ•˜๋‚˜ ๋ฆฌ์†Œ์Šค ์ƒํƒœ๋Š” ๋™์ผ).

์š”์ฒญ/์‘๋‹ต ๊ทœ์น™(๊ถŒ์žฅ)

  • ์š”์ฒญ: Content-Type: application/json; charset=utf-8
  • ์‘๋‹ต: Content-Type: application/json; charset=utf-8
  • ํƒ€์ž„์Šคํƒฌํ”„๋Š” ISO-8601 UTC(2025-04-25T12:34:56Z), ๊ธˆ์•ก/์ •๋ฐ€๋„๋Š” ๋ฌธ์ž์—ด ๋˜๋Š” ์ •์ˆ˜ ์„ผํŠธ๋‹จ์œ„ ๊ถŒ์žฅ.

ํŽ˜์ด์ง•/ํ•„ํ„ฐ/์ •๋ ฌ

  • ํŽ˜์ด์ง•: GET /items?page=1&size=20
  • ์ปค์„œ ๊ธฐ๋ฐ˜: GET /items?cursor=xxx&limit=20
  • ์ •๋ ฌ/ํ•„ํ„ฐ: ?sort=-createdAt&status=active (๋‚ด๋ฆผ์ฐจ์ˆœ์€ - ์ ‘๋‘)

์บ์‹ฑ

  • ์บ์‹œ ์ง€์‹œ์ž: Cache-Control: public, max-age=60(์ดˆ)
  • ์กฐ๊ฑด๋ถ€ ์š”์ฒญ: ETag/If-None-Match, Last-Modified/If-Modified-Since
  • 304 Not Modified๋กœ ๋Œ€์—ญํญ ์ ˆ๊ฐ.

์˜ค๋ฅ˜ ์‘๋‹ต ํฌ๋งท ์˜ˆ์‹œ(๊ถŒ์žฅ)

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "title is required",
    "details": [{ "field": "title", "reason": "required" }],
    "traceId": "9b8f..."
  }
}

๋ณด์•ˆ

  • HTTPS ํ•„์ˆ˜, ์ž…๋ ฅ ๊ฒ€์ฆ/์ถœ๋ ฅ ์ธ์ฝ”๋”ฉ, ๋ ˆ์ดํŠธ ๋ฆฌ๋ฐ‹ํŒ…/๋ด‡ ๋ฐฉ์–ด, CORS ์ตœ์†Œ ํ—ˆ์šฉ ์›์น™.
  • ์ธ์ฆ: Bearer JWT ๋˜๋Š” ์„ธ์…˜ ์ฟ ํ‚ค, ๋ฏผ๊ฐ์ •๋ณด๋Š” ์ ˆ๋Œ€ ๋กœ๊ทธ/์‘๋‹ต์— ๋…ธ์ถœ ๊ธˆ์ง€.

๋ฒ„์ „ ๊ด€๋ฆฌ

  • ๊ฒฝ๋กœ ๋ฒ„์ „: /v1/users
  • ํ—ค๋” ๋ฒ„์ „: Accept: application/vnd.myapp.v2+json
  • Major ๋ณ€๊ฒฝ์—๋งŒ ๋ฒ„์ „ ์ƒ์Šน, ๊ฐ€๋Šฅํ•œ ํ˜ธํ™˜์„ฑ ์œ ์ง€.

์ƒํƒœ ์ฝ”๋“œ ์ƒ์„ธ(์‹ค์ „ ์‚ฌ์šฉ ๊ฐ€์ด๋“œ)

  • 1xx Informational
    • 100 Continue: ๋Œ€์šฉ๋Ÿ‰ ์ „์†ก ์ „ ํ—ค๋” ์Šน์ธ(๋“œ๋ญ„)
    • 101 Switching Protocols: ์—…๊ทธ๋ ˆ์ด๋“œ(์›น์†Œ์ผ“ ๋“ฑ)
  • 2xx Success
    • 200 OK: ์ผ๋ฐ˜ ์„ฑ๊ณต(์กฐํšŒ/๊ฐฑ์‹  ๊ฒฐ๊ณผ ๋ณธ๋ฌธ ํฌํ•จ)
    • 201 Created: ์ƒ์„ฑ ์„ฑ๊ณต. Location ํ—ค๋”์— ์ƒˆ ๋ฆฌ์†Œ์Šค URI ํฌํ•จ, ๋ณธ๋ฌธ์— ๋ฆฌ์†Œ์Šค ํ‘œํ˜„
    • 202 Accepted: ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ ์ˆ˜๋ฝ(ํ์ž‰). ํด๋ง/์›นํ›…์œผ๋กœ ์™„๋ฃŒ ํ†ต์ง€
    • 204 No Content: ๋ณธ๋ฌธ ์—†๋Š” ์„ฑ๊ณต(DELETE/๋ฉฑ๋“ฑ PUT ๊ฒฐ๊ณผ ๋“ฑ)
    • 206 Partial Content: ๋ฒ”์œ„ ์‘๋‹ต(๋Œ€์šฉ๋Ÿ‰ ๋‹ค์šด๋กœ๋“œ)
  • 3xx Redirection
    • 301 Moved Permanently / 308 Permanent Redirect: ์˜๊ตฌ ์ด๋™(๋ฉ”์„œ๋“œ ๋ณด์กด์€ 308)
    • 302 Found / 303 See Other / 307 Temporary Redirect: ์ผ์‹œ์  ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ(303์€ GET์œผ๋กœ ์ „ํ™˜)
    • 304 Not Modified: ์กฐ๊ฑด๋ถ€ ์š”์ฒญ ์บ์‹œ ์ ์ค‘
  • 4xx Client Error
    • 400 Bad Request: ์ž˜๋ชป๋œ ๋ฌธ๋ฒ•/๋ฐ”๋””. ํ•„๋“œ ๊ฒ€์ฆ ์‹คํŒจ๋Š” 422 ๊ถŒ์žฅ
    • 401 Unauthorized: ์ธ์ฆ ํ•„์š”/๋งŒ๋ฃŒ(WWW-Authenticate ํฌํ•จ)
    • 403 Forbidden: ์ธ์ฆํ–ˆ์œผ๋‚˜ ๊ถŒํ•œ ์—†์Œ
    • 404 Not Found: ๋ฆฌ์†Œ์Šค ์—†์Œ(๋ณด์•ˆ ๊ด€์ ์—์„œ 403 ๋Œ€์‹  404 ๋งˆ์Šคํ‚น ๊ณ ๋ ค ๊ฐ€๋Šฅ)
    • 405 Method Not Allowed: ํ—ˆ์šฉ ๋ฉ”์„œ๋“œ ์•„๋‹˜(Allow ํ—ค๋” ํฌํ•จ)
    • 409 Conflict: ์ƒํƒœ ์ถฉ๋Œ(์ค‘๋ณต ํ‚ค/๋ฒ„์ „ ์ถฉ๋Œ/์ƒํƒœ ์ „์ด ๋ถˆ๊ฐ€)
    • 410 Gone: ์˜๊ตฌ ์‚ญ์ œ๋˜์–ด ๋” ์ด์ƒ ์‚ฌ์šฉ ๋ถˆ๊ฐ€
    • 412 Precondition Failed: ETag/์กฐ๊ฑด ํ—ค๋” ๋ถˆ์ผ์น˜(๋‚™๊ด€์  ๋ฝ)
    • 415 Unsupported Media Type: ์ง€์›ํ•˜์ง€ ์•Š๋Š” Content-Type
    • 422 Unprocessable Entity: ์˜๋ฏธ์ƒ ์œ ํšจํ•˜์ง€ ์•Š์Œ(ํ•„๋“œ ๊ฒ€์ฆ ์‹คํŒจ)
    • 429 Too Many Requests: ๋ ˆ์ดํŠธ ๋ฆฌ๋ฐ‹ ์ดˆ๊ณผ(Retry-After ํฌํ•จ)
  • 5xx Server Error
    • 500 Internal Server Error: ์ผ๋ฐ˜ ์„œ๋ฒ„ ์˜ค๋ฅ˜(๋ฏผ๊ฐ์ •๋ณด ๋…ธ์ถœ ๊ธˆ์ง€)
    • 501 Not Implemented: ๋ฏธ๊ตฌํ˜„ ๋ฉ”์„œ๋“œ/๊ธฐ๋Šฅ
    • 502 Bad Gateway: ์ƒ์œ„ ๊ฒŒ์ดํŠธ์›จ์ด/์—…์ŠคํŠธ๋ฆผ ์˜ค๋ฅ˜
    • 503 Service Unavailable: ์ ๊ฒ€/์ผ์‹œ ๊ณผ๋ถ€ํ•˜(Retry-After)
    • 504 Gateway Timeout: ์—…์ŠคํŠธ๋ฆผ ํƒ€์ž„์•„์›ƒ

์—”๋“œํฌ์ธํŠธ ์˜ˆ์‹œ

POST /v1/users HTTP/1.1
Content-Type: application/json

{ "email": "a@b.com", "password": "***" }
HTTP/1.1 201 Created
Location: /v1/users/123
Content-Type: application/json

{ "id": 123, "email": "a@b.com", "createdAt": "2025-04-25T12:34:56Z" }

๋ถ€๋ถ„ ์ˆ˜์ •(PATCH) ์˜ˆ์‹œ

PATCH /v1/users/123
Content-Type: application/json

{ "nickname": "js" }

์ถฉ๋Œ/๋ฝ(ETag) ์˜ˆ์‹œ

PUT /v1/users/123
If-Match: "etag-xyz"
Content-Type: application/json

{ "email": "c@d.com" }

์ฒดํฌ๋ฆฌ์ŠคํŠธ(์š”์•ฝ)

  • ๋ช…ํ™•ํ•œ ๋ฆฌ์†Œ์Šค ๋ชจ๋ธ๊ณผ ์ผ๊ด€๋œ URI/๋ฉ”์„œ๋“œ ๋งคํ•‘
  • ๋ฉฑ๋“ฑ์„ฑ ๋ณด์žฅ(ํŠนํžˆ PUT/DELETE)๊ณผ ์ ์ ˆํ•œ ์ƒํƒœ ์ฝ”๋“œ ์‚ฌ์šฉ
  • ํ‘œ์ค€ํ™”๋œ ์˜ค๋ฅ˜ ํฌ๋งท๊ณผ ํŠธ๋ ˆ์ด์Šค ID ์ œ๊ณต
  • ์บ์‹œ/์กฐ๊ฑด๋ถ€ ์š”์ฒญ/ETag๋กœ ๋Œ€์—ญํญ ์ ˆ๊ฐ
  • HTTPS, ์ธ์ฆ/๊ถŒํ•œ, ๋ ˆ์ดํŠธ ๋ฆฌ๋ฐ‹, ์ž…๋ ฅ ๊ฒ€์ฆ์œผ๋กœ ๋ณด์•ˆ ๊ฐ•ํ™”
  • ๋ฌธ์„œํ™”(OpenAPI/Swagger)์™€ ์ƒ˜ํ”Œ ์‘๋‹ต ์œ ์ง€