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)์ ์ํ ์๋ต ์ ์ง