Ver3.0 ๐ API ํต์ ๊ณผ RESTful ์๋น์ค, GraphQL์ ํ์ฉํ ๋ฐ์ดํฐ ์ฐ๋ ์์ ์ ๋ณต

๐ API ํต์ ๊ณผ RESTful ์๋น์ค, GraphQL์ ํ์ฉํ ๋ฐ์ดํฐ ์ฐ๋ ์์ ์ ๋ณต
๋ชจ๋ฐ์ผ ์ฑ ๊ฐ๋ฐ์๋ผ๋ฉด ๋ฐ๋์ ์์์ผ ํ ๋ฐ์ดํฐ ํต์ ์ ๋ชจ๋ ๊ฒ
๐ฑ ์ API ํต์ ์ด ๋ชจ๋ฐ์ผ ์ฑ์ ํต์ฌ์ผ๊น?
์๋
! ์ค๋์ ๋ชจ๋ฐ์ผ ์ฑ ๊ฐ๋ฐ์์ ์ ๋ ๋นผ๋์ ์ ์๋ API ํต์ ์ ๋ํด ์ด์ผ๊ธฐํด๋ณผ๊ฒ. ๐
์์ฆ ์ฐ๋ฆฌ๊ฐ ์ฌ์ฉํ๋ ๊ฑฐ์ ๋ชจ๋ ์ฑ๋ค, ์ธ์คํ๊ทธ๋จ๋ถํฐ ๋ฐฐ๋ฌ์๋ฏผ์กฑ, ์นด์นด์คํก๊น์ง ๋ชจ๋ ์๋ฒ์ ๋์์์ด ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ๊ณ ์์ด. ์ฌ์ง์ ์
๋ก๋ํ๊ณ , ๋ฉ์์ง๋ฅผ ๋ณด๋ด๊ณ , ์์์ ์ฃผ๋ฌธํ๋ ๋ชจ๋ ๊ณผ์ ์ด ๋ฐ๋ก API ํต์ ์ ํตํด ์ด๋ฃจ์ด์ง๋ ๊ฑฐ์ง.
๋ง์ฝ ๋ค๊ฐ ๋ชจ๋ฐ์ผ ์ฑ์ ๊ฐ๋ฐํ๊ณ ์๋ค๋ฉด, ํน์ ๊ฐ๋ฐ์ ๋ฐฐ์ฐ๊ณ ์ถ๋ค๋ฉด, API ํต์ ์ ํ์ ์ค์ ํ์์ผ. ์๋ฌด๋ฆฌ ๋ฉ์ง UI๋ฅผ ๋ง๋ค์ด๋ ์๋ฒ์ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์ง ๋ชปํ๋ฉด ๊ทธ๊ฑด ๊ทธ๋ฅ ์์ ๊ป๋ฐ๊ธฐ์ ๋ถ๊ณผํ๊ฑฐ๋ . ๐จ
๐ API๊ฐ ์ ํํ ๋ญ๊น?
API๋ Application Programming Interface์ ์ฝ์์ผ. ์ฝ๊ฒ ๋งํ๋ฉด ์๋ก ๋ค๋ฅธ ํ๋ก๊ทธ๋จ๋ค์ด ๋ํํ ์ ์๊ฒ ํด์ฃผ๋ ์ฝ์์ด๋ผ๊ณ ์๊ฐํ๋ฉด ๋ผ.
์๋น์ ๋น์ ํด๋ณผ๊ฒ. ๐ฝ๏ธ
๋๋ ์๋(ํด๋ผ์ด์ธํธ/์ฑ)์ด๊ณ , ์ฃผ๋ฐฉ์ ์๋ฒ์ผ. ๋๋ ์ฃผ๋ฐฉ์ ์ง์ ๋ค์ด๊ฐ์ "์ด๊ฑฐ ์ฃผ์ธ์!"๋ผ๊ณ ๋งํ ์ ์์์? ๋์ ์จ์ดํฐ(API)์๊ฒ ๋ฉ๋ดํ์ ๋ณด๊ณ ์ฃผ๋ฌธ์ ํ์ง. ์จ์ดํฐ๋ ๋ค ์ฃผ๋ฌธ์ ์ฃผ๋ฐฉ์ ์ ๋ฌํ๊ณ , ์์์ด ์ค๋น๋๋ฉด ๋ค์ ๋์๊ฒ ๊ฐ์ ธ๋ค์ค.
API๋ ๋๊ฐ์. ์ฑ์ ์๋ฒ์ ์ง์ ์ ๊ทผํ ์ ์๊ณ , API๋ผ๋ '์ค๊ฐ ๋ค๋ฆฌ'๋ฅผ ํตํด์๋ง ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ๊ณ ๋ฐ์ ์ ์์ด.
โข ์๋ฒ: ๋ฐ์ดํฐ๋ฅผ ์ ๊ณตํ๋ ์ชฝ
โข API: ๋ ์ฌ์ด์ ํต์ ๊ท์น๊ณผ ๋ฐฉ๋ฒ
โข ์์ฒญ(Request): ํด๋ผ์ด์ธํธ๊ฐ ์๋ฒ์ ๋ณด๋ด๋ ๋ฉ์์ง
โข ์๋ต(Response): ์๋ฒ๊ฐ ํด๋ผ์ด์ธํธ์๊ฒ ๋ณด๋ด๋ ๋ต๋ณ
๐ HTTP ํ๋กํ ์ฝ: API ํต์ ์ ๊ธฐ๋ณธ ์ธ์ด
API ํต์ ์ ๋๋ถ๋ถ HTTP(HyperText Transfer Protocol)๋ผ๋ ํ๋กํ ์ฝ์ ์ฌ์ฉํด. ์ด๊ฑด ์น ๋ธ๋ผ์ฐ์ ๊ฐ ์น์ฌ์ดํธ์ ํต์ ํ ๋ ์ฌ์ฉํ๋ ๊ทธ HTTP๊ฐ ๋ง์! ๐ก
HTTP๋ ์์ฒญ๊ณผ ์๋ต์ผ๋ก ์ด๋ฃจ์ด์ ธ ์์ด. ํด๋ผ์ด์ธํธ๊ฐ ์์ฒญ์ ๋ณด๋ด๋ฉด ์๋ฒ๊ฐ ์๋ต์ ๋๋ ค์ฃผ๋ ๋จ์ํ ๊ตฌ์กฐ์ง. ํ์ง๋ง ์ด ์์๋ ๊ฝค ๋ง์ ์ ๋ณด๊ฐ ๋ด๊ฒจ ์์ด.
๐ค HTTP ๋ฉ์๋(Methods)
HTTP ์์ฒญ์ ๋ณด๋ผ ๋๋ "์ด๋ค ์์ ์ ํ๊ณ ์ถ์์ง"๋ฅผ ๋ฉ์๋๋ก ํํํด. ์ฃผ์ ๋ฉ์๋ 4๊ฐ์ง๋ฅผ ์์๋ณด์:
| ๋ฉ์๋ | ์๋ฏธ | ์ค์ ์ฌ์ฉ ์์ |
|---|---|---|
| GET | ๋ฐ์ดํฐ ์กฐํ | ์ฌ์ฉ์ ํ๋กํ ๋ณด๊ธฐ, ๊ฒ์๊ธ ๋ชฉ๋ก ๊ฐ์ ธ์ค๊ธฐ |
| POST | ๋ฐ์ดํฐ ์์ฑ | ํ์๊ฐ์ , ์ ๊ฒ์๊ธ ์์ฑ, ๋๊ธ ๋ฌ๊ธฐ |
| PUT/PATCH | ๋ฐ์ดํฐ ์์ | ํ๋กํ ์ ๋ณด ๋ณ๊ฒฝ, ๊ฒ์๊ธ ์์ |
| DELETE | ๋ฐ์ดํฐ ์ญ์ | ๊ณ์ ์ญ์ , ๊ฒ์๊ธ ์ญ์ , ๋๊ธ ์ญ์ |
โข POST: "ํผ์ ํ ํ ์ฃผ๋ฌธํ ๊ฒ์" (์๋ก์ด ์ฃผ๋ฌธ ์์ฑ)
โข PUT: "์๊น ์ฃผ๋ฌธํ ํผ์, ํ ํ ๋ฐ๊ฟ์ฃผ์ธ์" (๊ธฐ์กด ์ฃผ๋ฌธ ์์ )
โข DELETE: "์ฃผ๋ฌธ ์ทจ์ํ ๊ฒ์" (์ฃผ๋ฌธ ์ญ์ )
๐ HTTP ์ํ ์ฝ๋(Status Codes)
์๋ฒ๊ฐ ์๋ต์ ๋ณด๋ผ ๋๋ ํญ์ ์ํ ์ฝ๋๋ฅผ ํจ๊ป ๋ณด๋ด. ์ด๊ฑด "์์ฒญ์ด ์ ์ฒ๋ฆฌ๋๋์ง, ๋ฌธ์ ๊ฐ ์์๋์ง"๋ฅผ ์๋ ค์ฃผ๋ ์ซ์์ผ. ๐ข
โ 2xx: ์ฑ๊ณต
200 OK์์ฒญ์ด ์ฑ๊ณต์ ์ผ๋ก ์ฒ๋ฆฌ๋จ
201 Created
์๋ก์ด ๋ฆฌ์์ค๊ฐ ์์ฑ๋จ
204 No Content
์ฑ๊ณตํ์ง๋ง ๋ณด๋ผ ๋ฐ์ดํฐ ์์
โ 4xx: ํด๋ผ์ด์ธํธ ์ค๋ฅ
400 Bad Request์๋ชป๋ ์์ฒญ
401 Unauthorized
์ธ์ฆ ํ์
404 Not Found
๋ฆฌ์์ค๋ฅผ ์ฐพ์ ์ ์์
๐ฅ 5xx: ์๋ฒ ์ค๋ฅ
500 Internal Server Error์๋ฒ ๋ด๋ถ ์ค๋ฅ
503 Service Unavailable
์๋ฒ๊ฐ ์ผ์์ ์ผ๋ก ์ฌ์ฉ ๋ถ๊ฐ
๐ฏ RESTful API: ๊ฐ์ฅ ๋๋ฆฌ ์ฐ์ด๋ API ์ค๊ณ ๋ฐฉ์
REST๋ Representational State Transfer์ ์ฝ์์ผ. ์ด๋ฆ์ ๋ณต์กํด ๋ณด์ด์ง๋ง, ์ฌ์ค ์น์ ์ฅ์ ์ ์ต๋ํ ํ์ฉํ๋ API ์ค๊ณ ๋ฐฉ์์ด๋ผ๊ณ ์๊ฐํ๋ฉด ๋ผ. ๐
2000๋
๋ ์ด๋ฐ ๋ก์ด ํ๋ฉ(Roy Fielding)์ด๋ผ๋ ์ปดํจํฐ ๊ณผํ์๊ฐ ์ ์ํ ๊ฐ๋
์ธ๋ฐ, ์ง๊ธ์ ๊ฑฐ์ ํ์ค์ฒ๋ผ ์ฌ์ฉ๋๊ณ ์์ด. ํนํ ๋ชจ๋ฐ์ผ ์ฑ ๊ฐ๋ฐ์์๋ REST API๊ฐ ์๋์ ์ผ๋ก ๋ง์ด ์ฐ์ฌ.
๐ REST์ 6๊ฐ์ง ์์น
ํด๋ผ์ด์ธํธ์ ์๋ฒ๋ ์๋ก ๋ ๋ฆฝ์ ์ด์ด์ผ ํด. ํด๋ผ์ด์ธํธ๋ UI์ ์ฌ์ฉ์ ๊ฒฝํ์๋ง ์ง์คํ๊ณ , ์๋ฒ๋ ๋ฐ์ดํฐ ์ ์ฅ๊ณผ ์ฒ๋ฆฌ์๋ง ์ง์คํ๋ ๊ฑฐ์ง. ์ด๋ ๊ฒ ํ๋ฉด ๊ฐ์ ๋ ๋ฆฝ์ ์ผ๋ก ๋ฐ์ ํ ์ ์์ด! ๐จ
์๋ฒ๋ ํด๋ผ์ด์ธํธ์ ์ํ๋ฅผ ์ ์ฅํ์ง ์์. ๊ฐ ์์ฒญ์ ๋ ๋ฆฝ์ ์ด๊ณ , ํ์ํ ๋ชจ๋ ์ ๋ณด๋ฅผ ์์ฒญ์ ํฌํจํด์ผ ํด. ์๋ฅผ ๋ค์ด, ๋ก๊ทธ์ธ ์ ๋ณด๋ ๋งค ์์ฒญ๋ง๋ค ํ ํฐ์ผ๋ก ๋ณด๋ด๋ ๊ฑฐ์ผ. ์ด๋ ๊ฒ ํ๋ฉด ์๋ฒ ํ์ฅ์ด ์ฌ์์ ธ! ๐
์๋ต ๋ฐ์ดํฐ๋ ์บ์ ๊ฐ๋ฅ ์ฌ๋ถ๋ฅผ ๋ช ์ํด์ผ ํด. ์์ฃผ ๋ฐ๋์ง ์๋ ๋ฐ์ดํฐ(์: ํ๋กํ ์ฌ์ง)๋ ์บ์ํด์ ๋คํธ์ํฌ ์์ฒญ์ ์ค์ผ ์ ์์ด. ์๋๋ ๋นจ๋ผ์ง๊ณ ์๋ฒ ๋ถ๋ด๋ ์ค์ด๋ค์ง! โก
ํด๋ผ์ด์ธํธ๋ ์ง์ ์๋ฒ์ ํต์ ํ๋์ง, ์ค๊ฐ ์๋ฒ(ํ๋ก์, ๊ฒ์ดํธ์จ์ด)๋ฅผ ๊ฑฐ์น๋์ง ์ ์ ์์ด์ผ ํด. ์ด๋ ๊ฒ ํ๋ฉด ๋ณด์, ๋ก๋ ๋ฐธ๋ฐ์ฑ ๋ฑ์ ์ ์ฐํ๊ฒ ์ถ๊ฐํ ์ ์์ด! ๐๏ธ
URI๋ก ๋ฆฌ์์ค๋ฅผ ์๋ณํ๊ณ , HTTP ๋ฉ์๋๋ก ํ์๋ฅผ ํํํ๋ ๋ฑ ์ผ๊ด๋ ์ธํฐํ์ด์ค๋ฅผ ์ฌ์ฉํด์ผ ํด. ์ด๊ฒ REST์ ํต์ฌ์ด์ผ! ๐ฏ
์๋ฒ๊ฐ ํด๋ผ์ด์ธํธ์๊ฒ ์คํ ๊ฐ๋ฅํ ์ฝ๋๋ฅผ ์ ์กํ ์ ์์ด. ์๋ฅผ ๋ค์ด JavaScript๋ฅผ ๋ณด๋ด์ ํด๋ผ์ด์ธํธ ๊ธฐ๋ฅ์ ํ์ฅํ๋ ๊ฑฐ์ง. ์ด๊ฑด ์ ํ์ฌํญ์ด๋ผ ๊ผญ ์งํฌ ํ์๋ ์์ด! ๐ป
๐ RESTful URI ์ค๊ณ ์์น
REST API์์ ๊ฐ์ฅ ์ค์ํ ๊ฑด URI(Uniform Resource Identifier) ์ค๊ณ์ผ. URI๋ ๋ฆฌ์์ค๋ฅผ ์๋ณํ๋ ์ฃผ์์ธ๋ฐ, ์ ์ค๊ณํ๋ฉด API๊ฐ ์ง๊ด์ ์ด๊ณ ์ฌ์ฉํ๊ธฐ ์ฌ์์ ธ. ๐
GET /users/123 - ID๊ฐ 123์ธ ์ฌ์ฉ์ ์กฐํ
POST /users - ์ ์ฌ์ฉ์ ์์ฑ
PUT /users/123 - ID๊ฐ 123์ธ ์ฌ์ฉ์ ์ ๋ณด ์์
DELETE /users/123 - ID๊ฐ 123์ธ ์ฌ์ฉ์ ์ญ์
GET /users/123/posts - ID๊ฐ 123์ธ ์ฌ์ฉ์์ ๊ฒ์๊ธ ๋ชฉ๋ก
GET /posts?author=123&category=tech - ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ก ํํฐ๋ง
POST /users/delete/123 โ (URI์ ํ์ ํํ ๊ธ์ง)
GET /users/123/ โ (๋ง์ง๋ง ์ฌ๋์ ๊ธ์ง)
GET /Users/123 โ (๋๋ฌธ์ ์ฌ์ฉ ๊ธ์ง, ์๋ฌธ์ ์ฌ์ฉ)
GET /user-posts โ (ํ์ดํ๋ณด๋ค๋ ์ธ๋์ค์ฝ์ด ๋๋ ๊ณ์ธต ๊ตฌ์กฐ ์ฌ์ฉ)
๐ฆ RESTful API ์์ฒญ/์๋ต ์์
์ค์ ๋ก REST API๊ฐ ์ด๋ป๊ฒ ๋์ํ๋์ง ๊ตฌ์ฒด์ ์ธ ์์๋ฅผ ๋ณด์. ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ์กฐํํ๋ ๊ฐ๋จํ ์๋๋ฆฌ์ค์ผ. ๐ฌ
GET /api/users/123 HTTP/1.1
Host: api.example.com
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Accept: application/json
User-Agent: MyMobileApp/1.0
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: max-age=3600
{
"id": 123,
"username": "jaenung_user",
"email": "user@jaenung.net",
"name": "๊น์ฌ๋ฅ",
"profile_image": "https://cdn.example.com/profiles/123.jpg",
"created_at": "2023-01-15T09:30:00Z",
"followers_count": 1250,
"following_count": 340
}
์ ์์๋ฅผ ๋ณด๋ฉด:
โข ์์ฒญ ํค๋์๋ ์ธ์ฆ ํ ํฐ, ๋ฐ๊ณ ์ถ์ ๋ฐ์ดํฐ ํ์ ๋ฑ์ด ํฌํจ๋ผ
โข ์๋ต ํค๋์๋ ์ํ ์ฝ๋(200), ๋ฐ์ดํฐ ํ์(JSON), ์บ์ ์ ์ฑ
๋ฑ์ด ์์ด
โข ์๋ต ๋ณธ๋ฌธ์๋ ์ค์ ์ฌ์ฉ์ ๋ฐ์ดํฐ๊ฐ JSON ํ์์ผ๋ก ๋ด๊ฒจ ์์ง
JSON์ JavaScript Object Notation์ ์ฝ์๋ก, ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์ ๋ ๊ฐ์ฅ ๋ง์ด ์ฐ์ด๋ ํ์์ด์ผ. ์ฌ๋์ด ์ฝ๊ธฐ๋ ์ฝ๊ณ , ํ๋ก๊ทธ๋จ์ด ํ์ฑํ๊ธฐ๋ ์ฌ์์ REST API์ ํ์ค ๋ฐ์ดํฐ ํ์์ผ๋ก ์๋ฆฌ ์ก์์ด. ๐
๐ GraphQL: REST์ ๋์์ด์ ์งํ
2015๋ ํ์ด์ค๋ถ์ด ๊ณต๊ฐํ GraphQL์ REST API์ ํ๊ณ๋ฅผ ๊ทน๋ณตํ๊ธฐ ์ํด ๋ง๋ค์ด์ง ์ฟผ๋ฆฌ ์ธ์ด์ผ. ์์ฆ ๋ง์ ๊ธฐ์ ๋ค์ด REST์์ GraphQL๋ก ์ ํํ๊ณ ์์ด. ์ ๊ทธ๋ด๊น? ๐ค
๐ REST vs GraphQL: ๋ฌด์์ด ๋ค๋ฅผ๊น?
๐ต REST API์ ๋ฌธ์ ์
Over-fetchingํ์ํ ๊ฒ๋ณด๋ค ๋ง์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์. ์๋ฅผ ๋ค์ด ์ฌ์ฉ์ ์ด๋ฆ๋ง ํ์ํ๋ฐ ํ๋กํ ์ ์ฒด๋ฅผ ๋ฐ๋ ๊ฒฝ์ฐ.
Under-fetching
ํ์ํ ๋ฐ์ดํฐ๋ฅผ ์ป๊ธฐ ์ํด ์ฌ๋ฌ ๋ฒ ์์ฒญํด์ผ ํจ. ์ฌ์ฉ์ ์ ๋ณด + ๊ฒ์๊ธ + ๋๊ธ์ ๊ฐ๊ฐ ์์ฒญ.
์๋ํฌ์ธํธ ์ฆ๊ฐ
๊ธฐ๋ฅ์ด ์ถ๊ฐ๋ ๋๋ง๋ค ์๋ก์ด ์๋ํฌ์ธํธ๋ฅผ ๋ง๋ค์ด์ผ ํจ.
๐ GraphQL์ ์ฅ์
์ ํํ ๋ฐ์ดํฐ ์์ฒญํ์ํ ํ๋๋ง ์ ํํ ์์ฒญํ๊ณ ๋ฐ์ ์ ์์.
๋จ์ผ ์์ฒญ
์ฌ๋ฌ ๋ฆฌ์์ค๋ฅผ ํ ๋ฒ์ ์์ฒญ์ผ๋ก ๊ฐ์ ธ์ฌ ์ ์์.
๊ฐ๋ ฅํ ํ์ ์์คํ
์คํค๋ง๋ก API ๊ตฌ์กฐ๊ฐ ๋ช ํํ ์ ์๋์ด ์์.
๐ GraphQL ์ฟผ๋ฆฌ ์์ฑํ๊ธฐ
GraphQL์ ๊ฐ์ฅ ํฐ ํน์ง์ ์ฟผ๋ฆฌ ์ธ์ด๋ฅผ ์ฌ์ฉํ๋ค๋ ๊ฑฐ์ผ. SQL์ฒ๋ผ ์ํ๋ ๋ฐ์ดํฐ๋ฅผ ๋ช ์์ ์ผ๋ก ์์ฒญํ ์ ์์ด. ์์๋ฅผ ๋ณด์! ๐
query {
user(id: "123") {
name
email
posts {
title
createdAt
comments {
content
author {
name
}
}
}
}
}
์ ์ฟผ๋ฆฌ ํ๋๋ก ์ฌ์ฉ์ ์ ๋ณด, ๊ฒ์๊ธ, ๋๊ธ, ๋๊ธ ์์ฑ์๊น์ง ํ ๋ฒ์ ๊ฐ์ ธ์ฌ ์ ์์ด! REST์๋ค๋ฉด ์ต์ 3-4๋ฒ์ ์์ฒญ์ด ํ์ํ์ ๊ฑฐ์ผ. ๐ฏ
mutation {
createPost(input: {
title: "GraphQL ์์ ์ ๋ณต"
content: "GraphQL์ ์ ๋ง ๊ฐ๋ ฅํ ๋๊ตฌ์
๋๋ค!"
authorId: "123"
}) {
id
title
createdAt
}
}
๋ฎคํ ์ด์ ์ ๋ฐ์ดํฐ๋ฅผ ์์ฑ, ์์ , ์ญ์ ํ ๋ ์ฌ์ฉํด. REST์ POST, PUT, DELETE์ ๋น์ทํ ์ญํ ์ด์ง. โจ
subscription {
newMessage(chatRoomId: "room-456") {
id
content
sender {
name
avatar
}
timestamp
}
}
๊ตฌ๋ ์ GraphQL์ ๋ ํนํ ๊ธฐ๋ฅ์ด์ผ. ์ค์๊ฐ์ผ๋ก ๋ฐ์ดํฐ ๋ณ๊ฒฝ์ ๋ฐ์ ์ ์์ด. ์ฑํ ์ฑ์ด๋ ์ค์๊ฐ ์๋ฆผ์ ์๋ฒฝํ์ง! ๐ฌ
๐๏ธ GraphQL ์คํค๋ง
GraphQL์ ํต์ฌ์ ์คํค๋ง(Schema)์ผ. ์คํค๋ง๋ API์ ์ฒญ์ฌ์ง์ผ๋ก, ์ด๋ค ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ ์ ์๋์ง, ๊ฐ ํ๋์ ํ์ ์ ๋ฌด์์ธ์ง ์ ์ํด. ๐
type User {
id: ID!
name: String!
email: String!
age: Int
posts: [Post!]!
followers: [User!]!
}
type Post {
id: ID!
title: String!
content: String!
author: User!
comments: [Comment!]!
createdAt: DateTime!
likes: Int!
}
type Comment {
id: ID!
content: String!
author: User!
post: Post!
createdAt: DateTime!
}
type Query {
user(id: ID!): User
users(limit: Int, offset: Int): [User!]!
post(id: ID!): Post
posts(authorId: ID): [Post!]!
}
type Mutation {
createUser(input: CreateUserInput!): User!
updateUser(id: ID!, input: UpdateUserInput!): User!
deleteUser(id: ID!): Boolean!
createPost(input: CreatePostInput!): Post!
}
์คํค๋ง๋ฅผ ๋ณด๋ฉด API์ ์ ์ฒด ๊ตฌ์กฐ๋ฅผ ํ๋์ ํ์ ํ ์ ์์ด. ! ํ์๋ ํ์ ํ๋๋ฅผ ์๋ฏธํ๊ณ , []๋ ๋ฐฐ์ด์ ์๋ฏธํด. ๐จ
โข ๊ฐ๋ฐ ์ค์ ์๋์์ฑ๊ณผ ํ์ ์ฒดํฌ๊ฐ ๊ฐ๋ฅํด
โข API ๋ฌธ์๊ฐ ์๋์ผ๋ก ์์ฑ๋ผ (GraphiQL, GraphQL Playground)
โข ํด๋ผ์ด์ธํธ์ ์๋ฒ ๊ฐ ๊ณ์ฝ์ด ๋ช ํํด์ ธ์ ์ค๋ฅ๊ฐ ์ค์ด๋ค์ด
โข ํ๋ก ํธ์๋์ ๋ฐฑ์๋ ๊ฐ๋ฐ์ ๊ฐ ์ํต์ด ์ฌ์์ ธ! ๐ค
๐ฑ ๋ชจ๋ฐ์ผ ์ฑ์์ API ํต์ ๊ตฌํํ๊ธฐ
์ด๋ก ์ ์ถฉ๋ถํ ๋ฐฐ์ ์ผ๋, ์ด์ ์ค์ ๋ก ๋ชจ๋ฐ์ผ ์ฑ์์ ์ด๋ป๊ฒ API ํต์ ์ ๊ตฌํํ๋์ง ์์๋ณด์! iOS, Android, React Native ๊ฐ๊ฐ์ ๋ฐฉ๋ฒ์ ์ดํด๋ณผ๊ฒ. ๐ ๏ธ
๐ iOS (Swift) - URLSession ์ฌ์ฉ
iOS์์๋ URLSession์ ์ฌ์ฉํด์ ๋คํธ์ํฌ ํต์ ์ ํด. ์ ํ์ด ์ ๊ณตํ๋ ๊ฐ๋ ฅํ ๋คํธ์ํน ํ๋ ์์ํฌ์ผ. ๐ฑ
import Foundation
struct User: Codable {
let id: Int
let name: String
let email: String
}
class APIService {
static let shared = APIService()
private let baseURL = "https://api.example.com"
func fetchUser(id: Int, completion: @escaping (Result<User, Error>) -> Void) {
guard let url = URL(string: "\(baseURL)/users/\(id)") else {
completion(.failure(NSError(domain: "Invalid URL", code: -1)))
return
}
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue("Bearer YOUR_TOKEN", forHTTPHeaderField: "Authorization")
URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
completion(.failure(error))
return
}
guard let data = data else {
completion(.failure(NSError(domain: "No data", code: -1)))
return
}
do {
let user = try JSONDecoder().decode(User.self, from: data)
completion(.success(user))
} catch {
completion(.failure(error))
}
}.resume()
}
}
// ์ฌ์ฉ ์์
APIService.shared.fetchUser(id: 123) { result in
switch result {
case .success(let user):
print("์ฌ์ฉ์ ์ด๋ฆ: \(user.name)")
case .failure(let error):
print("์๋ฌ ๋ฐ์: \(error.localizedDescription)")
}
}
struct CreateUserRequest: Codable {
let name: String
let email: String
let password: String
}
func createUser(request: CreateUserRequest, completion: @escaping (Result<User, Error>) -> Void) {
guard let url = URL(string: "\(baseURL)/users") else {
completion(.failure(NSError(domain: "Invalid URL", code: -1)))
return
}
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
do {
urlRequest.httpBody = try JSONEncoder().encode(request)
} catch {
completion(.failure(error))
return
}
URLSession.shared.dataTask(with: urlRequest) { data, response, error in
// ์๋ต ์ฒ๋ฆฌ (์์ ๋์ผ)
}.resume()
}
๐ค Android (Kotlin) - Retrofit ์ฌ์ฉ
Android์์๋ Retrofit์ด๋ผ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ง์ด ์ฌ์ฉํด. Square์ฌ์์ ๋ง๋ ๊ฐ๋ ฅํ HTTP ํด๋ผ์ด์ธํธ์ผ. ๐ง
// build.gradle (Module)
dependencies {
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0'
}
// API ์ธํฐํ์ด์ค ์ ์
interface ApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") userId: Int): Response<User>
@GET("users")
suspend fun getUsers(
@Query("limit") limit: Int,
@Query("offset") offset: Int
): Response<List<User>>
@POST("users")
suspend fun createUser(@Body request: CreateUserRequest): Response<User>
@PUT("users/{id}")
suspend fun updateUser(
@Path("id") userId: Int,
@Body request: UpdateUserRequest
): Response<User>
@DELETE("users/{id}")
suspend fun deleteUser(@Path("id") userId: Int): Response<Unit>
}
// Retrofit ์ธ์คํด์ค ์์ฑ
object RetrofitClient {
private const val BASE_URL = "https://api.example.com/"
private val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
private val client = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.addInterceptor { chain ->
val request = chain.request().newBuilder()
.addHeader("Authorization", "Bearer YOUR_TOKEN")
.build()
chain.proceed(request)
}
.build()
val apiService: ApiService by lazy {
Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(ApiService::class.java)
}
}
class UserViewModel : ViewModel() {
private val _user = MutableLiveData<User>()
val user: LiveData<User> = _user
private val _error = MutableLiveData<String>()
val error: LiveData<String> = _error
fun fetchUser(userId: Int) {
viewModelScope.launch {
try {
val response = RetrofitClient.apiService.getUser(userId)
if (response.isSuccessful) {
_user.value = response.body()
} else {
_error.value = "์๋ฌ: ${response.code()}"
}
} catch (e: Exception) {
_error.value = "๋คํธ์ํฌ ์๋ฌ: ${e.message}"
}
}
}
fun createUser(name: String, email: String, password: String) {
viewModelScope.launch {
try {
val request = CreateUserRequest(name, email, password)
val response = RetrofitClient.apiService.createUser(request)
if (response.isSuccessful) {
_user.value = response.body()
} else {
_error.value = "์์ฑ ์คํจ: ${response.code()}"
}
} catch (e: Exception) {
_error.value = "์๋ฌ: ${e.message}"
}
}
}
}
โ๏ธ React Native - Axios ์ฌ์ฉ
React Native์์๋ Axios๋ Fetch API๋ฅผ ์ฌ์ฉํด. Axios๊ฐ ๋ ํธ๋ฆฌํ ๊ธฐ๋ฅ์ ์ ๊ณตํด์ ๋ง์ด ์ฐ์ฌ. ๐
// ์ค์น
// npm install axios
import axios from 'axios';
// API ํด๋ผ์ด์ธํธ ์ค์
const apiClient = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
// ์ธํฐ์
ํฐ๋ก ํ ํฐ ์๋ ์ถ๊ฐ
apiClient.interceptors.request.use(
async (config) => {
const token = await AsyncStorage.getItem('authToken');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// ์๋ต ์ธํฐ์
ํฐ๋ก ์๋ฌ ์ฒ๋ฆฌ
apiClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
// ํ ํฐ ๋ง๋ฃ ์ฒ๋ฆฌ
// ๋ก๊ทธ์ธ ํ๋ฉด์ผ๋ก ์ด๋
}
return Promise.reject(error);
}
);
// API ํจ์๋ค
export const userAPI = {
getUser: async (userId) => {
try {
const response = await apiClient.get(`/users/${userId}`);
return response.data;
} catch (error) {
throw error;
}
},
createUser: async (userData) => {
try {
const response = await apiClient.post('/users', userData);
return response.data;
} catch (error) {
throw error;
}
},
updateUser: async (userId, userData) => {
try {
const response = await apiClient.put(`/users/${userId}`, userData);
return response.data;
} catch (error) {
throw error;
}
},
deleteUser: async (userId) => {
try {
await apiClient.delete(`/users/${userId}`);
} catch (error) {
throw error;
}
},
};
// React ์ปดํฌ๋ํธ์์ ์ฌ์ฉ
import React, { useState, useEffect } from 'react';
import { View, Text, ActivityIndicator } from 'react-native';
const UserProfile = ({ userId }) => {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
fetchUser();
}, [userId]);
const fetchUser = async () => {
try {
setLoading(true);
const userData = await userAPI.getUser(userId);
setUser(userData);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
};
if (loading) return <ActivityIndicator />;
if (error) return <Text>์๋ฌ: {error}</Text>;
return (
<View>
<Text>{user.name}</Text>
<Text>{user.email}</Text>
</View>
);
};
๐ API ๋ณด์: ์์ ํ ํต์ ์ ์ํ ํ์ ์์
API ํต์ ์์ ๋ณด์์ ์ ๋ง ์ค์ํด! ์ฌ์ฉ์์ ๊ฐ์ธ์ ๋ณด์ ๋ฏผ๊ฐํ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃจ๊ธฐ ๋๋ฌธ์ด์ง. ๋ชจ๋ฐ์ผ ์ฑ ๊ฐ๋ฐ์๋ผ๋ฉด ๋ฐ๋์ ์์์ผ ํ ๋ณด์ ๊ฐ๋ ๋ค์ ์์๋ณด์. ๐ก๏ธ
๐ HTTPS: ์ํธํ๋ ํต์
HTTPS(HTTP Secure)๋ HTTP์ SSL/TLS ์ํธํ๋ฅผ ์ถ๊ฐํ ํ๋กํ ์ฝ์ด์ผ. ๋ชจ๋ ๋ฐ์ดํฐ๊ฐ ์ํธํ๋์ด ์ ์ก๋๊ธฐ ๋๋ฌธ์ ์ค๊ฐ์์ ๊ฐ๋ก์ฑ๋ ๋ด์ฉ์ ์ ์ ์์ด. ๐
์์ฆ์ HTTP๋ฅผ ์ฌ์ฉํ๋ ๊ฑด ๊ฑฐ์ ์์ด ํ์์ผ. ์ ํ๊ณผ ๊ตฌ๊ธ ๋ชจ๋ ์ฑ ์คํ ์ด์ ๋ฑ๋กํ๋ ค๋ฉด HTTPS๋ฅผ ํ์๋ก ์๊ตฌํ๊ณ ์์ด. HTTP ํต์ ์ ๊ธฐ๋ณธ์ ์ผ๋ก ์ฐจ๋จ๋๊ฑฐ๋ !
โข ๋ฐ์ดํฐ๊ฐ ํ๋ฌธ์ผ๋ก ์ ์ก๋จ
โข ์ค๊ฐ์ ๊ณต๊ฒฉ(Man-in-the-Middle)์ ์ทจ์ฝ
โข ๋น๋ฐ๋ฒํธ, ๊ฐ์ธ์ ๋ณด ์ ์ถ ์ํ
HTTPS (์์ ํจ)
โข ๋ชจ๋ ๋ฐ์ดํฐ๊ฐ ์ํธํ๋จ
โข ์๋ฒ ์ธ์ฆ์๋ก ์ ๋ขฐ์ฑ ๋ณด์ฅ
โข ๋ฐ์ดํฐ ๋ฌด๊ฒฐ์ฑ ๋ณด์ฅ
๐ซ ์ธ์ฆ(Authentication): ๋๋ ๋๊ตฌ๋?
API๋ฅผ ํธ์ถํ๋ ์ฌ๋์ด ์ ๋ง ๊ถํ์ด ์๋ ์ฌ์ฉ์์ธ์ง ํ์ธํ๋ ๊ณผ์ ์ด ์ธ์ฆ์ด์ผ. ์ฌ๋ฌ ๋ฐฉ๋ฒ์ด ์๋๋ฐ, ๊ฐ์ฅ ๋ง์ด ์ฐ์ด๋ ๋ฐฉ์๋ค์ ์์๋ณด์. ๐ค
๊ฐ์ฅ ์ธ๊ธฐ ์๋ ์ธ์ฆ ๋ฐฉ์์ด์ผ. ๋ก๊ทธ์ธํ๋ฉด ์๋ฒ๊ฐ ํ ํฐ์ ๋ฐ๊ธํ๊ณ , ์ดํ ๋ชจ๋ ์์ฒญ์ ์ด ํ ํฐ์ ํฌํจ์์ผ. ํ ํฐ ์์๋ ์ฌ์ฉ์ ์ ๋ณด๊ฐ ์ํธํ๋์ด ์์ด.
// JWT ๊ตฌ์กฐ
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. // Header
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ. // Payload
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c // Signature
// ์ฌ์ฉ ์์
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
"๊ตฌ๊ธ๋ก ๋ก๊ทธ์ธ", "์นด์นด์ค๋ก ๋ก๊ทธ์ธ" ๊ฐ์ ์์ ๋ก๊ทธ์ธ์ ์ฌ์ฉ๋๋ ํ๋กํ ์ฝ์ด์ผ. ์ฌ์ฉ์๊ฐ ์ง์ ๋น๋ฐ๋ฒํธ๋ฅผ ์ ๋ ฅํ์ง ์๊ณ ๋ ์์ ํ๊ฒ ์ธ์ฆํ ์ ์์ด. ๐
OAuth ํ๋ฆ:
1. ์ฌ์ฉ์๊ฐ "๊ตฌ๊ธ๋ก ๋ก๊ทธ์ธ" ํด๋ฆญ
2. ๊ตฌ๊ธ ๋ก๊ทธ์ธ ํ์ด์ง๋ก ์ด๋
3. ์ฌ์ฉ์๊ฐ ๊ถํ ์น์ธ
4. ์ฑ์ด ์ก์ธ์ค ํ ํฐ ๋ฐ์
5. ํ ํฐ์ผ๋ก ์ฌ์ฉ์ ์ ๋ณด ์์ฒญ
๊ฐ๋จํ ์ธ์ฆ ๋ฐฉ์์ผ๋ก, ๊ณ ์ ํ ํค๋ฅผ ๋ฐ๊ธ๋ฐ์ ์์ฒญ์ ํฌํจ์์ผ. ์ฃผ๋ก ์๋ฒ ๊ฐ ํต์ ์ด๋ ๊ณต๊ฐ API์์ ์ฌ์ฉํด.
// ํค๋์ ํฌํจ
X-API-Key: your-api-key-here
// ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ก ํฌํจ
GET /api/data?api_key=your-api-key-here
๐ช ์ธ๊ฐ(Authorization): ๋๋ ์ด๊ฑธ ํ ์ ์๋?
์ธ์ฆ์ด "์ ์ ํ์ธ"์ด๋ผ๋ฉด, ์ธ๊ฐ๋ "๊ถํ ํ์ธ"์ด์ผ. ๋ก๊ทธ์ธํ ์ฌ์ฉ์๋ผ๋ ๋ชจ๋ ๊ฑธ ํ ์ ์๋ ๊ฑด ์๋์์? ์๋ฅผ ๋ค์ด ์ผ๋ฐ ์ฌ์ฉ์๋ ๋ค๋ฅธ ์ฌ๋์ ๊ฒ์๊ธ์ ์ญ์ ํ ์ ์์ง. ๐ซ
์ฌ์ฉ์์๊ฒ ์ญํ (Role)์ ๋ถ์ฌํ๊ณ , ์ญํ ์ ๋ฐ๋ผ ๊ถํ์ ๋ค๋ฅด๊ฒ ์ค.
โข Admin: ๋ชจ๋ ๊ถํ
โข Moderator: ๊ฒ์๊ธ ๊ด๋ฆฌ, ์ฌ์ฉ์ ์ ์ง
โข User: ์๊ธฐ ๊ฒ์๊ธ๋ง ์์ /์ญ์
โข Guest: ์ฝ๊ธฐ๋ง ๊ฐ๋ฅ
// ์๋ฒ์์ ๊ถํ ์ฒดํฌ
if (user.role !== 'admin' && post.authorId !== user.id) {
return res.status(403).json({ error: '๊ถํ์ด ์์ต๋๋ค' });
}
๐ก๏ธ ๊ธฐํ ๋ณด์ Best Practices
๐ Rate Limiting
API ํธ์ถ ํ์๋ฅผ ์ ํํด์ ๋จ์ฉ์ ๋ฐฉ์งํด. ์๋ฅผ ๋ค์ด 1๋ถ์ 100๋ฒ๊น์ง๋ง ์์ฒญ ๊ฐ๋ฅํ๊ฒ ํ๋ ๊ฑฐ์ง. DDoS ๊ณต๊ฒฉ๋ ๋ง์ ์ ์์ด!๐ ์ ๋ ฅ ๊ฒ์ฆ
ํด๋ผ์ด์ธํธ์์ ๋ณด๋ธ ๋ฐ์ดํฐ๋ฅผ ์ ๋ ๋ฏฟ์ง ๋ง! ์๋ฒ์์ ํญ์ ๊ฒ์ฆํด์ผ ํด. SQL Injection, XSS ๊ฐ์ ๊ณต๊ฒฉ์ ๋ง์ ์ ์์ด.๐ ๋ฏผ๊ฐ ์ ๋ณด ์ํธํ
๋น๋ฐ๋ฒํธ๋ ์ ๋ ํ๋ฌธ์ผ๋ก ์ ์ฅํ๋ฉด ์ ๋ผ! bcrypt ๊ฐ์ ํด์ ํจ์๋ก ์ํธํํด์ผ ํด. ๊ฐ์ธ์ ๋ณด๋ ์ํธํํด์ ์ ์ฅํ๋ ๊ฒ ์ข์.โข ์ค์ํ ๋ฐ์ดํฐ๋ ๊ธฐ๊ธฐ์ ์ํธํํด์ ์ ์ฅํด (Keychain, KeyStore ์ฌ์ฉ).
โข ๋ฃจํ /ํ์ฅ๋ ๊ธฐ๊ธฐ์์๋ ๋ฏผ๊ฐํ ๊ธฐ๋ฅ์ ์ ํํด.
โข SSL Pinning์ผ๋ก ์ค๊ฐ์ ๊ณต๊ฒฉ์ ๋ฐฉ์ดํด.
โข ์ฑ ์ ๋ฐ์ดํธ๋ฅผ ํตํด ๋ณด์ ํจ์น๋ฅผ ๋น ๋ฅด๊ฒ ๋ฐฐํฌํด! ๐
โก ์ฑ๋ฅ ์ต์ ํ: ๋น ๋ฅด๊ณ ํจ์จ์ ์ธ API ํต์
API ํต์ ์ ๋คํธ์ํฌ๋ฅผ ์ฌ์ฉํ๊ธฐ ๋๋ฌธ์ ๋๋ฆด ์ ์์ด. ํนํ ๋ชจ๋ฐ์ผ ํ๊ฒฝ์์๋ ๋คํธ์ํฌ ์ํ๊ฐ ๋ถ์์ ํ ์ ์์ง. ์ฑ๋ฅ์ ์ต์ ํํ๋ ๋ฐฉ๋ฒ๋ค์ ์์๋ณด์! ๐
๐พ ์บ์ฑ(Caching): ๊ฐ์ ๋ฐ์ดํฐ ๋ ๋ฒ ๋ฐ์ง ์๊ธฐ
์บ์ฑ์ ํ ๋ฒ ๋ฐ์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํด๋๋ค๊ฐ ์ฌ์ฌ์ฉํ๋ ๊ฑฐ์ผ. ๋คํธ์ํฌ ์์ฒญ์ ์ค์ฌ์ ์๋๋ ๋นจ๋ผ์ง๊ณ ๋ฐ์ดํฐ ์ฌ์ฉ๋๋ ์ค์ด๋ค์ด. ๐ฆ
1. HTTP ์บ์ฑ
์๋ฒ๊ฐ ์๋ต ํค๋์ ์บ์ ์ ์ฑ ์ ๋ช ์ํด.
Cache-Control: max-age=3600 // 1์๊ฐ ๋์ ์บ์
ETag: "33a64df551425fcc55e4d42a148795d9f25f89d4" // ๋ฆฌ์์ค ๋ฒ์
2. ๋ฉ๋ชจ๋ฆฌ ์บ์ฑ
์ฑ ๋ฉ๋ชจ๋ฆฌ์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅ. ๋น ๋ฅด์ง๋ง ์ฑ ์ข ๋ฃํ๋ฉด ์ฌ๋ผ์ ธ.
3. ๋์คํฌ ์บ์ฑ
๊ธฐ๊ธฐ ์ ์ฅ์์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅ. ๋๋ฆฌ์ง๋ง ์๊ตฌ์ ์ผ๋ก ๋ณด๊ด๋ผ.
4. ์กฐ๊ฑด๋ถ ์์ฒญ
๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋์ ๋๋ง ์๋ก ๋ฐ์.
If-None-Match: "33a64df551425fcc55e4d42a148795d9f25f89d4"
// ์๋ฒ: ๋ณ๊ฒฝ ์์ผ๋ฉด 304 Not Modified ์๋ต
๐ฆ ๋ฐ์ดํฐ ์์ถ: ์ ์ก๋ ์ค์ด๊ธฐ
๋ฐ์ดํฐ๋ฅผ ์์ถํด์ ๋ณด๋ด๋ฉด ๋คํธ์ํฌ ์ฌ์ฉ๋์ ํฌ๊ฒ ์ค์ผ ์ ์์ด. ํนํ ์ด๋ฏธ์ง๋ ํฐ JSON ๋ฐ์ดํฐ์ ํจ๊ณผ์ ์ด์ผ. ๐๏ธ
๋๋ถ๋ถ์ ์๋ฒ์ ํด๋ผ์ด์ธํธ๊ฐ GZIP ์์ถ์ ์ง์ํด. ์์ฒญ ํค๋์ ๋ช ์ํ๋ฉด ์๋ฒ๊ฐ ์๋์ผ๋ก ์์ถํด์ ๋ณด๋ด์ค.
// ์์ฒญ ํค๋
Accept-Encoding: gzip, deflate
// ์๋ต ํค๋
Content-Encoding: gzip
Content-Length: 1234 // ์์ถ๋ ํฌ๊ธฐ
JSON ๋ฐ์ดํฐ๋ ๋ณดํต 70-90% ์ ๋ ์์ถ๋ผ. ์์ฒญ๋ ์ ์ฝ์ด์ง! ๐ฐ
๐ ํ์ด์ง๋ค์ด์ : ํ ๋ฒ์ ์กฐ๊ธ์ฉ
์์ฒ ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ํ ๋ฒ์ ๋ฐ์ผ๋ฉด ๋๋ฆฌ๊ณ ๋ฉ๋ชจ๋ฆฌ๋ ๋ง์ด ์จ. ํ์ด์ง๋ค์ด์ ์ผ๋ก ๋๋ ์ ๋ฐ์! ๐
๐ Offset ๊ธฐ๋ฐ
GET /posts?limit=20&offset=0
GET /posts?limit=20&offset=20
GET /posts?limit=20&offset=40
๊ฐ๋จํ์ง๋ง ๋ฐ์ดํฐ๊ฐ ์ถ๊ฐ/์ญ์ ๋๋ฉด ์ค๋ณต์ด๋ ๋๋ฝ์ด ์๊ธธ ์ ์์ด.
๐ Cursor ๊ธฐ๋ฐ
GET /posts?limit=20
GET /posts?limit=20&cursor=abc123
GET /posts?limit=20&cursor=def456
๋ง์ง๋ง ํญ๋ชฉ์ ID๋ฅผ ๊ธฐ์ค์ผ๋ก ๋ค์ ํ์ด์ง๋ฅผ ๊ฐ์ ธ์. ๋ ์์ ์ ์ด์ผ!
๐ฏ ํ๋ ์ ํ: ํ์ํ ๊ฒ๋ง ๋ฐ๊ธฐ
GraphQL์ ์ฅ์ ์ REST์์๋ ๊ตฌํํ ์ ์์ด. ํ์ํ ํ๋๋ง ์์ฒญํ๋ ๊ฑฐ์ง! ๐จ
// ๋ชจ๋ ํ๋ (๋นํจ์จ์ )
GET /users/123
{
"id": 123,
"username": "jaenung_user",
"email": "user@example.com",
"bio": "...",
"avatar": "...",
"followers_count": 1250,
"following_count": 340,
"posts_count": 89,
"created_at": "...",
"updated_at": "...",
// ... ๋ ๋ง์ ํ๋๋ค
}
// ํ์ํ ํ๋๋ง (ํจ์จ์ )
GET /users/123?fields=id,username,avatar
{
"id": 123,
"username": "jaenung_user",
"avatar": "..."
}
๐ ๋ฐฐ์น ์์ฒญ: ์ฌ๋ฌ ์์ฒญ์ ํ๋๋ก
์ฌ๋ฌ ๊ฐ์ API๋ฅผ ํธ์ถํด์ผ ํ ๋, ๊ฐ๊ฐ ์์ฒญํ๋ฉด ๋๋ ค. ๋ฐฐ์น๋ก ๋ฌถ์ด์ ํ ๋ฒ์ ๋ณด๋ด์! ๐ฎ
POST /batch
{
"requests": [
{ "method": "GET", "url": "/users/123" },
{ "method": "GET", "url": "/users/123/posts" },
{ "method": "GET", "url": "/users/123/followers" }
]
}
// ์๋ต
{
"responses": [
{ "status": 200, "body": { /* user data */ } },
{ "status": 200, "body": [ /* posts */ ] },
{ "status": 200, "body": [ /* followers */ ] }
]
}
๋คํธ์ํฌ ์๋ณต ์๊ฐ์ ํฌ๊ฒ ์ค์ผ ์ ์์ด! โก
โข ์ ์ ํ ํฌ๊ธฐ: ์ธ๋ค์ผ์ ์๊ฒ, ์์ธ ์ด๋ฏธ์ง๋ ํฌ๊ฒ
โข WebP ํฌ๋งท: JPEG๋ณด๋ค 30% ์์ผ๋ฉด์ ํ์ง์ ๋น์ทํด
โข Lazy Loading: ํ๋ฉด์ ๋ณด์ผ ๋๋ง ๋ก๋
โข CDN ์ฌ์ฉ: ์ ์ธ๊ณ ์๋ฒ์์ ๋น ๋ฅด๊ฒ ์ ๊ณต
โข Progressive JPEG: ์ ์ง์ ์ผ๋ก ๋ก๋ฉ๋์ด ์ฌ์ฉ์ ๊ฒฝํ ๊ฐ์ ! ๐ผ๏ธ
๐ ์๋ฌ ์ฒ๋ฆฌ: ๋ฌธ์ ๊ฐ ์๊ฒผ์ ๋ ๋์ฒํ๊ธฐ
๋คํธ์ํฌ๋ ํญ์ ๋ถ์์ ํด. ์ธํฐ๋ท์ด ๋๊ธฐ๊ฑฐ๋, ์๋ฒ๊ฐ ๋ค์ด๋๊ฑฐ๋, ํ์์์์ด ๋ฐ์ํ ์ ์์ด. ์ข์ ์๋ฌ ์ฒ๋ฆฌ๋ ์ฌ์ฉ์ ๊ฒฝํ์ ํฌ๊ฒ ๊ฐ์ ํด! ๐ฏ
๐ ์๋ฌ ์๋ต ์ค๊ณ
์๋ฒ๋ ์๋ฌ๊ฐ ๋ฐ์ํ์ ๋ ๋ช ํํ ์ ๋ณด๋ฅผ ์ ๊ณตํด์ผ ํด. ๋จ์ํ "์๋ฌ ๋ฐ์"์ด ์๋๋ผ "๋ฌด์์ด ์๋ชป๋๋์ง" ์๋ ค์ค์ผ์ง. ๐ฌ
{
"error": {
"code": "INVALID_EMAIL",
"message": "์ด๋ฉ์ผ ํ์์ด ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค.",
"details": {
"field": "email",
"value": "invalid-email",
"constraint": "์ด๋ฉ์ผ์ '@'๋ฅผ ํฌํจํด์ผ ํฉ๋๋ค."
},
"timestamp": "2024-01-15T10:30:00Z",
"request_id": "req_abc123"
}
}
ํฌํจํด์ผ ํ ์ ๋ณด:
โข ์๋ฌ ์ฝ๋: ํ๋ก๊ทธ๋๋ฐ์ ์ผ๋ก ์ฒ๋ฆฌ ๊ฐ๋ฅํ ๊ณ ์ ์ฝ๋
โข ๋ฉ์์ง: ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ค ์น์ ํ ์ค๋ช
โข ์์ธ ์ ๋ณด: ๋๋ฒ๊น ์ ๋์๋๋ ์ถ๊ฐ ์ ๋ณด
โข ํ์์คํฌํ: ์ธ์ ๋ฐ์ํ๋์ง
โข ์์ฒญ ID: ๋ก๊ทธ ์ถ์ ์ฉ
๐ ์ฌ์๋(Retry) ์ ๋ต
์ผ์์ ์ธ ๋คํธ์ํฌ ๋ฌธ์ ๋ก ์คํจํ ์์ฒญ์ ์๋์ผ๋ก ์ฌ์๋ํ๋ ๊ฒ ์ข์. ํ์ง๋ง ๋ฌดํ์ ์ฌ์๋ํ๋ฉด ์ ๋๊ฒ ์ง? ๐
์ฌ์๋ ๊ฐ๊ฒฉ์ ์ ์ ๋๋ ค๊ฐ๋ ์ ๋ต์ด์ผ. ์๋ฒ ๋ถํ๋ ์ค์ด๊ณ ์ฑ๊ณต ํ๋ฅ ๋ ๋์ฌ!
// ์ฌ์๋ ๋ก์ง ์์ (JavaScript)
async function fetchWithRetry(url, options = {}, maxRetries = 3) {
let lastError;
for (let i = 0; i < maxRetries; i++) {
try {
const response = await fetch(url, options);
// ์ฑ๊ณตํ๋ฉด ๋ฐ๋ก ๋ฐํ
if (response.ok) {
return response;
}
// 4xx ์๋ฌ๋ ์ฌ์๋ ์ ํจ (ํด๋ผ์ด์ธํธ ๋ฌธ์ )
if (response.status >= 400 && response.status < 500) {
throw new Error(`Client error: ${response.status}`);
}
// 5xx ์๋ฌ๋ ์ฌ์๋
lastError = new Error(`Server error: ${response.status}`);
} catch (error) {
lastError = error;
}
// ๋ง์ง๋ง ์๋๊ฐ ์๋๋ฉด ๋๊ธฐ
if (i < maxRetries - 1) {
const delay = Math.pow(2, i) * 1000; // 1์ด, 2์ด, 4์ด...
await new Promise(resolve => setTimeout(resolve, delay));
}
}
throw lastError;
}
// ์ฌ์ฉ ์์
try {
const response = await fetchWithRetry('https://api.example.com/data');
const data = await response.json();
} catch (error) {
console.error('๋ชจ๋ ์ฌ์๋ ์คํจ:', error);
}
โฑ๏ธ ํ์์์ ์ค์
์์ฒญ์ด ๋๋ฌด ์ค๋ ๊ฑธ๋ฆฌ๋ฉด ์ฌ์ฉ์๋ ๋ต๋ตํดํด. ์ ์ ํ ํ์์์์ ์ค์ ํ์! โฐ
โก ๋น ๋ฅธ ์์ฒญ
5-10์ด์ฌ์ฉ์ ์ ๋ณด ์กฐํ, ๋ก๊ทธ์ธ ๋ฑ ๋น ๋ฅธ ์๋ต์ด ํ์ํ ๊ฒฝ์ฐ
๐ ์ผ๋ฐ ์์ฒญ
15-30์ด๋ชฉ๋ก ์กฐํ, ๊ฒ์ ๋ฑ ์ผ๋ฐ์ ์ธ ๋ฐ์ดํฐ ์์ฒญ
๐ค ์ ๋ก๋
60์ด ์ด์ํ์ผ ์ ๋ก๋, ๋์ฉ๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ๋ฑ
๐จ ์ฌ์ฉ์ ๊ฒฝํ ๊ฐ์
์๋ฌ๊ฐ ๋ฐ์ํด๋ ์ฌ์ฉ์๊ฐ ๋นํฉํ์ง ์๋๋ก ์น์ ํ๊ฒ ์๋ดํ์! ๐
์์ฒญ ์ค์์ ๋ช ํํ ํ์ํด. ์คํผ๋, ํ๋ก๊ทธ๋ ์ค ๋ฐ, ์ค์ผ๋ ํค UI ๋ฑ์ ์ฌ์ฉํ์. ์ฌ์ฉ์๋ ๋ญ๊ฐ ์งํ๋๊ณ ์๋ค๋ ๊ฑธ ์์์ผ ์์ฌํด! โณ
"Error 500"๋ณด๋ค๋ "์ผ์์ ์ธ ๋ฌธ์ ๊ฐ ๋ฐ์ํ์ด์. ์ ์ ํ ๋ค์ ์๋ํด์ฃผ์ธ์."๊ฐ ํจ์ฌ ์ข์. ๊ธฐ์ ์ฉ์ด๋ ํผํ๊ณ ์ฌ์ด ๋ง๋ก ์ค๋ช ํ์! ๐ฌ
์๋ฌ ํ๋ฉด์ "๋ค์ ์๋" ๋ฒํผ์ ์ ๊ณตํด. ์ฌ์ฉ์๊ฐ ์ง์ ์ฌ์๋ํ ์ ์๊ฒ ํ๋ ๊ฑฐ์ง. ์ฑ์ ๊ป๋ค ์ผ๋ ๊ฒ๋ณด๋ค ํจ์ฌ ํธํด! ๐
์ธํฐ๋ท์ด ๋๊ฒผ์ ๋๋ ๊ธฐ๋ณธ์ ์ธ ๊ธฐ๋ฅ์ ๋์ํ๊ฒ ํ์. ์บ์๋ ๋ฐ์ดํฐ๋ฅผ ๋ณด์ฌ์ฃผ๊ฑฐ๋, ๋์ค์ ๋๊ธฐํํ ์์ ์ ํ์ ์ ์ฅํ๋ ๊ฑฐ์ผ! ๐ด
ํนํ ๋ชจ๋ฐ์ผ ์ฑ์ ๋ค์ํ ๊ธฐ๊ธฐ์ OS ๋ฒ์ ์์ ๋์ํ๊ธฐ ๋๋ฌธ์, ์๋ฌ ๋ชจ๋ํฐ๋ง์ด ์ ๋ง ์ค์ํด. ์ด๋ค ๊ธฐ๊ธฐ์์ ์ด๋ค ์๋ฌ๊ฐ ๋ง์ด ๋ฐ์ํ๋์ง ํ์ ํด์ผ ์ฐ์ ์์๋ฅผ ์ ํ ์ ์๊ฑฐ๋ !
๐งช API ํ ์คํธ: ์ ๋๋ก ๋์ํ๋์ง ํ์ธํ๊ธฐ
API๋ฅผ ๊ฐ๋ฐํ๋ค๋ฉด ์ ๋๋ก ๋์ํ๋์ง ํ ์คํธํด์ผ๊ฒ ์ง? ์ฌ๋ฌ ํ ์คํธ ๋๊ตฌ์ ๋ฐฉ๋ฒ์ ์์๋ณด์! ๐ฌ
๐ ๏ธ API ํ ์คํธ ๋๊ตฌ๋ค
๐ฎ Postman
๊ฐ์ฅ ์ธ๊ธฐ ์๋ API ํ ์คํธ ๋๊ตฌ์ผ. GUI๊ฐ ์ง๊ด์ ์ด๊ณ ๊ธฐ๋ฅ๋ ๊ฐ๋ ฅํด. ์ปฌ๋ ์ ์ผ๋ก ์์ฒญ์ ๊ด๋ฆฌํ๊ณ , ์๋ํ ํ ์คํธ๋ ์์ฑํ ์ ์์ด. ๋ฌด๋ฃ ๋ฒ์ ๋ ์ถฉ๋ถํ ๊ฐ๋ ฅํด! ๐โก Insomnia
Postman์ ๋์์ผ๋ก, ๋ ๊ฐ๋ณ๊ณ ๋น ๋ฅด๋ค๋ ํ๊ฐ๋ฅผ ๋ฐ์. GraphQL ์ง์์ด ํนํ ์ข์. ๋์์ธ๋ ๊น๋ํ๊ณ ์ฌ์ฉํ๊ธฐ ์ฌ์! ๐จ๐ป cURL
์ปค๋งจ๋๋ผ์ธ ๋๊ตฌ๋ก, ๊ฐ๋ฐ์๋ค์ด ๊ฐ์ฅ ๋ง์ด ์ฌ์ฉํด. ์คํฌ๋ฆฝํธ๋ก ์๋ํํ๊ธฐ ์ฝ๊ณ , ์ด๋์๋ ์ฌ์ฉํ ์ ์์ด. ๋ฐฐ์ฐ๊ธฐ๋ ์กฐ๊ธ ์ด๋ ต์ง๋ง ๊ฐ๋ ฅํด! ๐ง# GET ์์ฒญ
curl https://api.example.com/users/123
# POST ์์ฒญ (JSON)
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"name":"๊น์ฌ๋ฅ","email":"user@jaenung.net"}'
# ์๋ต ํค๋ ๋ณด๊ธฐ
curl -i https://api.example.com/users/123
# ์๋ต์ ํ์ผ๋ก ์ ์ฅ
curl -o response.json https://api.example.com/users/123
# ํ์์์ ์ค์
curl --max-time 10 https://api.example.com/users/123
๐งช ์๋ํ ํ ์คํธ
์๋์ผ๋ก ํ ์คํธํ๋ ๊ฑด ์๊ฐ์ด ์ค๋ ๊ฑธ๋ ค. ์๋ํ ํ ์คํธ๋ฅผ ์์ฑํ๋ฉด ์ฝ๋ ๋ณ๊ฒฝ ํ ๋น ๋ฅด๊ฒ ๊ฒ์ฆํ ์ ์์ด! ๐ค
// api.test.js
const axios = require('axios');
describe('User API', () => {
const baseURL = 'https://api.example.com';
let authToken;
let userId;
// ํ
์คํธ ์ ์ ๋ก๊ทธ์ธ
beforeAll(async () => {
const response = await axios.post(`${baseURL}/auth/login`, {
email: 'test@example.com',
password: 'password123'
});
authToken = response.data.token;
});
// ์ฌ์ฉ์ ์์ฑ ํ
์คํธ
test('should create a new user', async () => {
const response = await axios.post(
`${baseURL}/users`,
{
name: 'ํ
์คํธ ์ฌ์ฉ์',
email: 'newuser@example.com',
password: 'password123'
},
{
headers: { Authorization: `Bearer ${authToken}` }
}
);
expect(response.status).toBe(201);
expect(response.data).toHaveProperty('id');
expect(response.data.name).toBe('ํ
์คํธ ์ฌ์ฉ์');
userId = response.data.id;
});
// ์ฌ์ฉ์ ์กฐํ ํ
์คํธ
test('should get user by id', async () => {
const response = await axios.get(`${baseURL}/users/${userId}`, {
headers: { Authorization: `Bearer ${authToken}` }
});
expect(response.status).toBe(200);
expect(response.data.id).toBe(userId);
});
// ์ฌ์ฉ์ ์์ ํ
์คํธ
test('should update user', async () => {
const response = await axios.put(
`${baseURL}/users/${userId}`,
{ name: '์์ ๋ ์ด๋ฆ' },
{
headers: { Authorization: `Bearer ${authToken}` }
}
);
expect(response.status).toBe(200);
expect(response.data.name).toBe('์์ ๋ ์ด๋ฆ');
});
// ๊ถํ ์๋ ์ ๊ทผ ํ
์คํธ
test('should return 401 without token', async () => {
try {
await axios.get(`${baseURL}/users/${userId}`);
} catch (error) {
expect(error.response.status).toBe(401);
}
});
// ์ฌ์ฉ์ ์ญ์ ํ
์คํธ
test('should delete user', async () => {
const response = await axios.delete(`${baseURL}/users/${userId}`, {
headers: { Authorization: `Bearer ${authToken}` }
});
expect(response.status).toBe(204);
});
// ์ญ์ ๋ ์ฌ์ฉ์ ์กฐํ ์ 404
test('should return 404 for deleted user', async () => {
try {
await axios.get(`${baseURL}/users/${userId}`, {
headers: { Authorization: `Bearer ${authToken}` }
});
} catch (error) {
expect(error.response.status).toBe(404);
}
});
});
๐ ์ฑ๋ฅ ํ ์คํธ
API๊ฐ ๋ง์ ์ฌ์ฉ์๋ฅผ ๊ฐ๋นํ ์ ์๋์ง ํ ์คํธํ๋ ๊ฒ๋ ์ค์ํด. ๋ถํ ํ ์คํธ๋ก ํ๊ณ๋ฅผ ํ์ ํ์! ๐ช
โข ์คํธ๋ ์ค ํ ์คํธ: ์์คํ ํ๊ณ๊น์ง ๋ถํ๋ฅผ ๊ฐํด ์ด๋์ ๊นจ์ง๋์ง ํ์ธ
โข ์คํ์ดํฌ ํ ์คํธ: ๊ฐ์์ค๋ฌ์ด ํธ๋ํฝ ์ฆ๊ฐ ์ํฉ ์๋ฎฌ๋ ์ด์
โข ์ง์์ฑ ํ ์คํธ: ์ฅ์๊ฐ ๋์ ์์ ์ ์ผ๋ก ๋์ํ๋์ง ํ์ธ
์ธก์ ์งํ:
โข ์๋ต ์๊ฐ (Response Time)
โข ์ฒ๋ฆฌ๋ (Throughput) - ์ด๋น ์์ฒญ ์
โข ์๋ฌ์จ (Error Rate)
โข CPU/๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋ฅ
๐ ์ค์ ํ๋ก์ ํธ: ๊ฐ๋จํ SNS ์ฑ ๋ง๋ค๊ธฐ
์ง๊ธ๊น์ง ๋ฐฐ์ด ๋ด์ฉ์ ์ข ํฉํด์ ์ค์ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์ด๋ณด์! ๊ฐ๋จํ SNS ์ฑ์ API ํต์ ๋ถ๋ถ์ ๊ตฌํํด๋ณผ๊ฒ. ๐จ
๐๏ธ ํ๋ก์ ํธ ๊ตฌ์กฐ
๐ 1๋จ๊ณ: ์ธ์ฆ ๊ตฌํ
// API ์๋ํฌ์ธํธ
POST /api/auth/login
// ์์ฒญ ๋ฐ๋
{
"email": "user@example.com",
"password": "password123"
}
// ์๋ต (์ฑ๊ณต)
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": 123,
"username": "jaenung_user",
"email": "user@example.com",
"avatar": "https://cdn.example.com/avatars/123.jpg"
}
}
// React Native ๊ตฌํ
const login = async (email, password) => {
try {
const response = await apiClient.post('/auth/login', {
email,
password
});
// ํ ํฐ ์ ์ฅ
await AsyncStorage.setItem('authToken', response.data.token);
await AsyncStorage.setItem('user', JSON.stringify(response.data.user));
return response.data;
} catch (error) {
if (error.response?.status === 401) {
throw new Error('์ด๋ฉ์ผ ๋๋ ๋น๋ฐ๋ฒํธ๊ฐ ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค.');
}
throw new Error('๋ก๊ทธ์ธ ์ค ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค.');
}
};
๐ 2๋จ๊ณ: ๊ฒ์๊ธ ํผ๋ ๊ตฌํ
// API ์๋ํฌ์ธํธ
GET /api/posts?limit=20&cursor=abc123
// ์๋ต
{
"posts": [
{
"id": 456,
"content": "์ฌ๋ฅ๋ท์์ ๋ฉ์ง ํ๋ก์ ํธ๋ฅผ ์์ํ์ด์!",
"author": {
"id": 123,
"username": "jaenung_user",
"avatar": "..."
},
"images": ["https://cdn.example.com/posts/456_1.jpg"],
"likes_count": 42,
"comments_count": 7,
"is_liked": false,
"created_at": "2024-01-15T10:30:00Z"
},
// ... ๋ ๋ง์ ๊ฒ์๊ธ
],
"next_cursor": "def456",
"has_more": true
}
// React Native ๊ตฌํ (React Query ์ฌ์ฉ)
import { useInfiniteQuery } from '@tanstack/react-query';
const useFeed = () => {
return useInfiniteQuery({
queryKey: ['feed'],
queryFn: async ({ pageParam = null }) => {
const url = pageParam
? `/posts?limit=20&cursor=${pageParam}`
: '/posts?limit=20';
const response = await apiClient.get(url);
return response.data;
},
getNextPageParam: (lastPage) => {
return lastPage.has_more ? lastPage.next_cursor : undefined;
},
});
};
// ์ปดํฌ๋ํธ์์ ์ฌ์ฉ
const FeedScreen = () => {
const {
data,
fetchNextPage,
hasNextPage,
isFetchingNextPage,
isLoading,
} = useFeed();
const posts = data?.pages.flatMap(page => page.posts) ?? [];
return (
<FlatList
data={posts}
renderItem={({ item }) => <PostCard post={item} />}
onEndReached={() => {
if (hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
}}
onEndReachedThreshold={0.5}
ListFooterComponent={
isFetchingNextPage ? <ActivityIndicator /> : null
}
/>
);
};
โค๏ธ 3๋จ๊ณ: ์ข์์ ๊ธฐ๋ฅ ๊ตฌํ
// API ์๋ํฌ์ธํธ
POST /api/posts/{postId}/like
DELETE /api/posts/{postId}/like
// React Native ๊ตฌํ
import { useMutation, useQueryClient } from '@tanstack/react-query';
const useLikePost = () => {
const queryClient = useQueryClient();
return useMutation({
mutationFn: async ({ postId, isLiked }) => {
if (isLiked) {
await apiClient.delete(`/posts/${postId}/like`);
} else {
await apiClient.post(`/posts/${postId}/like`);
}
},
// ๋๊ด์ ์
๋ฐ์ดํธ: ์๋ฒ ์๋ต ์ ์ UI ๋จผ์ ์
๋ฐ์ดํธ
onMutate: async ({ postId, isLiked }) => {
// ์งํ ์ค์ธ ์ฟผ๋ฆฌ ์ทจ์
await queryClient.cancelQueries({ queryKey: ['feed'] });
// ์ด์ ๋ฐ์ดํฐ ๋ฐฑ์
const previousData = queryClient.getQueryData(['feed']);
// ๋๊ด์ ์ผ๋ก UI ์
๋ฐ์ดํธ
queryClient.setQueryData(['feed'], (old) => {
return {
...old,
pages: old.pages.map(page => ({
...page,
posts: page.posts.map(post =>
post.id === postId
? {
...post,
is_liked: !isLiked,
likes_count: post.likes_count + (isLiked ? -1 : 1)
}
: post
)
}))
};
});
return { previousData };
},
// ์๋ฌ ๋ฐ์ ์ ๋กค๋ฐฑ
onError: (err, variables, context) => {
queryClient.setQueryData(['feed'], context.previousData);
},
// ์ฑ๊ณต ์ ๋ฐ์ดํฐ ์ฌ๊ฒ์ฆ
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ['feed'] });
},
});
};
// ์ปดํฌ๋ํธ์์ ์ฌ์ฉ
const PostCard = ({ post }) => {
const likePost = useLikePost();
const handleLike = () => {
likePost.mutate({
postId: post.id,
isLiked: post.is_liked
});
};
return (
<TouchableOpacity onPress={handleLike}>
<Icon
name={post.is_liked ? 'heart' : 'heart-outline'}
color={post.is_liked ? '#e74c3c' : '#95a5a6'}
/>
<Text>{post.likes_count}</Text>
</TouchableOpacity>
);
};
๋๊ด์ ์ ๋ฐ์ดํธ๋ ์ฌ์ฉ์ ๊ฒฝํ์ ํฌ๊ฒ ๊ฐ์ ํด! ์๋ฒ ์๋ต์ ๊ธฐ๋ค๋ฆฌ์ง ์๊ณ ์ฆ์ UI๋ฅผ ์ ๋ฐ์ดํธํ๊ธฐ ๋๋ฌธ์ ์ฑ์ด ํจ์ฌ ๋น ๋ฅด๊ฒ ๋๊ปด์ ธ. ๋ง์ฝ ์๋ฒ ์์ฒญ์ด ์คํจํ๋ฉด ์๋์ผ๋ก ๋กค๋ฐฑ๋๋ ์์ ํ๊ธฐ๋ ํด! โก
๐ฌ 4๋จ๊ณ: ์ค์๊ฐ ๋๊ธ (GraphQL Subscription)
// GraphQL Subscription
subscription OnNewComment($postId: ID!) {
newComment(postId: $postId) {
id
content
author {
id
username
avatar
}
created_at
}
}
// React Native ๊ตฌํ (Apollo Client)
import { useSubscription } from '@apollo/client';
const CommentsScreen = ({ postId }) => {
const [comments, setComments] = useState([]);
// ๊ธฐ์กด ๋๊ธ ๋ก๋
useEffect(() => {
loadComments();
}, [postId]);
// ์ ๋๊ธ ๊ตฌ๋
useSubscription(NEW_COMMENT_SUBSCRIPTION, {
variables: { postId },
onData: ({ data }) => {
const newComment = data.data.newComment;
setComments(prev => [newComment, ...prev]);
// ์๋ฆผ ํ์
showNotification(`${newComment.author.username}๋์ด ๋๊ธ์ ๋จ๊ฒผ์ต๋๋ค.`);
},
});
return (
<FlatList
data={comments}
renderItem={({ item }) => <CommentItem comment={item} />}
/>
);
};
โข ์ด๋ฏธ์ง ์ ๋ก๋: Multipart/form-data๋ก ํ์ผ ์ ์ก
โข ํธ์ ์๋ฆผ: FCM(Firebase Cloud Messaging) ์ฐ๋
โข ๊ฒ์ ๊ธฐ๋ฅ: Debouncing์ผ๋ก ๋ถํ์ํ ์์ฒญ ์ค์ด๊ธฐ
โข ์คํ๋ผ์ธ ์ง์: ๋คํธ์ํฌ ์์ ๋๋ ๊ธฐ๋ณธ ๊ธฐ๋ฅ ๋์
โข ์๋ฌ ๋ฐ์ด๋๋ฆฌ: ์์์น ๋ชปํ ์๋ฌ ์ฒ๋ฆฌ
โข ์ฑ๋ฅ ๋ชจ๋ํฐ๋ง: ๋๋ฆฐ API ํธ์ถ ์ถ์
โข A/B ํ ์คํ : ์ ๊ธฐ๋ฅ ์ ์ง์ ๋ฐฐํฌ! ๐
๐ REST vs GraphQL: ์ด๋ค ๊ฑธ ์ ํํด์ผ ํ ๊น?
๋ ๋ค ๋ฐฐ์ ์ผ๋ ์ด์ ๊ถ๊ธํ๊ฒ ์ง? "๋ด ํ๋ก์ ํธ์๋ ๋ญ ์จ์ผ ํ์ง?" ๐ค
์ ๋ต์... ์ํฉ์ ๋ฐ๋ผ ๋ค๋ฅด๋ค! ๊ฐ๊ฐ์ ์ฅ๋จ์ ์ ๋น๊ตํด๋ณด๊ณ ํ๋ก์ ํธ์ ๋ง๋ ๊ฑธ ์ ํํ์.
| ๊ธฐ์ค | REST API | GraphQL |
|---|---|---|
| ํ์ต ๊ณก์ | โ ์ฌ์ - HTTP ๊ธฐ๋ณธ๋ง ์๋ฉด ๋จ | โ ๏ธ ์ด๋ ค์ - ์๋ก์ด ๊ฐ๋ ํ์ต ํ์ |
| ์ ์ฐ์ฑ | โ ๏ธ ์ ํ์ - ๊ณ ์ ๋ ์๋ํฌ์ธํธ | โ ๋งค์ฐ ์ ์ฐ - ์ํ๋ ๋ฐ์ดํฐ๋ง ์์ฒญ |
| Over-fetching | โ ์์ฃผ ๋ฐ์ | โ ์์ |
| Under-fetching | โ ์ฌ๋ฌ ์์ฒญ ํ์ | โ ํ ๋ฒ์ ํด๊ฒฐ |
| ์บ์ฑ | โ HTTP ์บ์ฑ ํ์ฉ ์ฌ์ | โ ๏ธ ๋ณต์กํจ - ๋ณ๋ ์ ๋ต ํ์ |
| ํ์ผ ์ ๋ก๋ | โ ๊ฐ๋จํจ | โ ๏ธ ๋ณต์กํจ |
| ์ค์๊ฐ ๊ธฐ๋ฅ | โ ๏ธ WebSocket ๋ณ๋ ๊ตฌํ | โ Subscription ๋ด์ฅ |
| ์๋ฌ ์ฒ๋ฆฌ | โ HTTP ์ํ ์ฝ๋ ํ์ฉ | โ ๏ธ ํญ์ 200, ์๋ฌ๋ ์๋ต ์์ |
| ๋๊ตฌ ์ํ๊ณ | โ ์ฑ์ํจ - ๋ง์ ๋๊ตฌ | โ ๋น ๋ฅด๊ฒ ์ฑ์ฅ ์ค |
| ๋ชจ๋ฐ์ผ ์ ํฉ์ฑ | โ ์ข์ | โ ๋งค์ฐ ์ข์ - ๋ฐ์ดํฐ ์ ์ฝ |
๐ฏ ์ ํ ๊ฐ์ด๋
๐ต REST๋ฅผ ์ ํํ์ธ์
โข ๊ฐ๋จํ CRUD ์ ํ๋ฆฌ์ผ์ด์ โข ํ์ด REST์ ์ต์ํจ
โข ๋น ๋ฅธ ํ๋กํ ํ์ดํ ํ์
โข ํ์ผ ์ ๋ก๋๊ฐ ๋ง์
โข HTTP ์บ์ฑ ํ์ฉํ๊ณ ์ถ์
โข ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ
์์: ๋ธ๋ก๊ทธ, ๊ฐ๋จํ ์ผํ๋ชฐ, ๊ด๋ฆฌ์ ๋์๋ณด๋
๐ GraphQL์ ์ ํํ์ธ์
โข ๋ณต์กํ ๋ฐ์ดํฐ ๊ด๊ณโข ๋ค์ํ ํด๋ผ์ด์ธํธ (์น, ๋ชจ๋ฐ์ผ, IoT)
โข ๋คํธ์ํฌ ํจ์จ์ฑ์ด ์ค์
โข ์ค์๊ฐ ๊ธฐ๋ฅ ํ์
โข ๋น ๋ฅธ ํ๋ก ํธ์๋ ๊ฐ๋ฐ
โข ๊ฐ๋ ฅํ ํ์ ์์คํ ์ํจ
์์: SNS, ๋์๋ณด๋, ํ์ ๋๊ตฌ, ์ค์๊ฐ ์ฑํ
โข REST: ํ์ผ ์ ๋ก๋, ์ธ์ฆ, ๊ฒฐ์ ๋ฑ ๋จ์ํ ์์
โข GraphQL: ๋ณต์กํ ๋ฐ์ดํฐ ์กฐํ, ์ค์๊ฐ ๊ธฐ๋ฅ
์๋ฅผ ๋ค์ด GitHub๋ REST API์ GraphQL API๋ฅผ ๋ชจ๋ ์ ๊ณตํ๊ณ ์์ด. ์ฌ์ฉ์๊ฐ ์ํฉ์ ๋ง๊ฒ ์ ํํ ์ ์๋๋ก ๋ง์ด์ผ! ๐จ
๐ ์ต์ ํธ๋ ๋์ ๋ฏธ๋
API ํต์ ๊ธฐ์ ์ ๊ณ์ ๋ฐ์ ํ๊ณ ์์ด. ์์ผ๋ก ์ฃผ๋ชฉํด์ผ ํ ํธ๋ ๋๋ค์ ์์๋ณด์! ๐ฎ
โก HTTP/3์ QUIC
HTTP/3๋ ์ฐจ์ธ๋ HTTP ํ๋กํ ์ฝ์ด์ผ. UDP ๊ธฐ๋ฐ์ QUIC ํ๋กํ ์ฝ์ ์ฌ์ฉํด์ ๋ ๋น ๋ฅด๊ณ ์์ ์ ์ธ ํต์ ์ ์ ๊ณตํด. ํนํ ๋ชจ๋ฐ์ผ ํ๊ฒฝ์์ ๋คํธ์ํฌ๊ฐ ์์ฃผ ๋ฐ๋ ๋ ์ ๋ฆฌํด! ๐ก
โข Head-of-Line Blocking ํด๊ฒฐ: ํจํท ์์ค ์์๋ ๋ค๋ฅธ ์คํธ๋ฆผ ์ํฅ ์์
โข ์ฐ๊ฒฐ ๋ง์ด๊ทธ๋ ์ด์ : Wi-Fi์์ ๋ชจ๋ฐ์ผ ๋ฐ์ดํฐ๋ก ์ ํํด๋ ์ฐ๊ฒฐ ์ ์ง
โข ๋ด์ฅ ์ํธํ: ๋ณด์์ด ๊ธฐ๋ณธ์ผ๋ก ์ ์ฉ๋จ
์ฃผ์ ๋ธ๋ผ์ฐ์ ์ CDN๋ค์ด ์ด๋ฏธ HTTP/3๋ฅผ ์ง์ํ๊ณ ์์ด. ๊ณง ํ์ค์ด ๋ ๊ฑฐ์ผ! ๐
๐ gRPC: ๊ณ ์ฑ๋ฅ RPC ํ๋ ์์ํฌ
gRPC๋ ๊ตฌ๊ธ์ด ๋ง๋ ๊ณ ์ฑ๋ฅ RPC(Remote Procedure Call) ํ๋ ์์ํฌ์ผ. Protocol Buffers๋ฅผ ์ฌ์ฉํด์ JSON๋ณด๋ค ํจ์ฌ ๋น ๋ฅด๊ณ ํจ์จ์ ์ด์ผ. ๐๏ธ
โข ์๋ฐฉํฅ ์คํธ๋ฆฌ๋ฐ: ์ค์๊ฐ ํต์ ์ ์ต์
โข ๊ฐ๋ ฅํ ํ์ ์์คํ : Protocol Buffers ์คํค๋ง
โข ๋ค์ํ ์ธ์ด ์ง์: ์๋ ์ฝ๋ ์์ฑ
์ฃผ๋ก ๋ง์ดํฌ๋ก์๋น์ค ๊ฐ ํต์ ์ ์ฌ์ฉ๋์ง๋ง, ๋ชจ๋ฐ์ผ ์ฑ์์๋ ์ ์ ๋ง์ด ์ฐ์ด๊ณ ์์ด!
๐ ์๋ฒ๋ฆฌ์ค์ Edge Computing
์๋ฒ๋ฆฌ์ค(Serverless) ์ํคํ ์ฒ์ ์ฃ์ง ์ปดํจํ (Edge Computing)์ด API ๊ฐ๋ฐ ๋ฐฉ์์ ๋ฐ๊พธ๊ณ ์์ด. ๐
โ๏ธ ์๋ฒ๋ฆฌ์ค
AWS Lambda, Google Cloud Functions ๊ฐ์ ์๋น์ค๋ก ์๋ฒ ๊ด๋ฆฌ ์์ด API๋ฅผ ๋ง๋ค ์ ์์ด. ์ฌ์ฉํ ๋งํผ๋ง ๋น์ฉ์ ๋ด๋ ๊ฒฝ์ ์ ์ด์ผ!์ฅ์ :
โข ์๋ ์ค์ผ์ผ๋ง
โข ๋ฎ์ ์ด๊ธฐ ๋น์ฉ
โข ๋น ๋ฅธ ๋ฐฐํฌ
๐ ์ฃ์ง ์ปดํจํ
์ฌ์ฉ์์ ๊ฐ๊น์ด ๊ณณ์์ API๋ฅผ ์คํํด์ ์ง์ฐ ์๊ฐ์ ์ค์ฌ. Cloudflare Workers, Vercel Edge Functions ๋ฑ์ด ์์ด.์ฅ์ :
โข ์ด์ ์ง์ฐ
โข ๊ธ๋ก๋ฒ ๋ฐฐํฌ
โข ํฅ์๋ ์ฌ์ฉ์ ๊ฒฝํ
๐ค AI์ API์ ๊ฒฐํฉ
ChatGPT, Midjourney ๊ฐ์ AI ์๋น์ค๋ค์ด API๋ก ์ ๊ณต๋๋ฉด์ ์๋ก์ด ๊ฐ๋ฅ์ฑ์ด ์ด๋ฆฌ๊ณ ์์ด. ๋ชจ๋ฐ์ผ ์ฑ์ AI ๊ธฐ๋ฅ์ ์ฝ๊ฒ ํตํฉํ ์ ์๊ฒ ๋์ง! ๐ง
โข Google Vision API: ์ด๋ฏธ์ง ์ธ์, OCR
โข AWS Rekognition: ์ผ๊ตด ์ธ์, ๊ฐ์ฒด ํ์ง
โข Stability AI: ์ด๋ฏธ์ง ์์ฑ
โข ElevenLabs: ์์ฑ ํฉ์ฑ
์ด๋ฐ API๋ค์ ํ์ฉํ๋ฉด ๋ณต์กํ AI ๋ชจ๋ธ์ ์ง์ ๊ฐ๋ฐํ์ง ์์๋ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ์ ์ฑ์ ์ถ๊ฐํ ์ ์์ด!
๐ ์ ๋ก ํธ๋ฌ์คํธ ๋ณด์
์ ๋ก ํธ๋ฌ์คํธ(Zero Trust) ๋ณด์ ๋ชจ๋ธ์ด API ๋ณด์์ ์๋ก์ด ํ์ค์ด ๋๊ณ ์์ด. "์ ๋ ์ ๋ขฐํ์ง ๋ง๊ณ , ํญ์ ๊ฒ์ฆํ๋ผ"๋ ์์น์ด์ผ. ๐ก๏ธ
โข ์ต์ ๊ถํ ์์น ์ ์ฉ
โข ๋ง์ดํฌ๋ก ์ธ๊ทธ๋ฉํ ์ด์
โข ์ง์์ ์ธ ๋ชจ๋ํฐ๋ง๊ณผ ๊ฒ์ฆ
โข ์ํธํ๋ ํต์ ํ์
ํนํ ๋ชจ๋ฐ์ผ ์ฑ์ ์ ๋ขฐํ ์ ์๋ ํ๊ฒฝ์์ ์คํ๋๊ธฐ ๋๋ฌธ์ ์ ๋ก ํธ๋ฌ์คํธ๊ฐ ๋์ฑ ์ค์ํด!
๐ผ ์ค๋ฌด์์ ์์ฃผ ๋ง์ฃผ์น๋ ๋ฌธ์ ๋ค๊ณผ ํด๊ฒฐ์ฑ
์ด๋ก ์ ์๋ฒฝํด๋ ์ค๋ฌด์์๋ ์์์น ๋ชปํ ๋ฌธ์ ๋ค์ด ์๊ฒจ. ๊ฒฝํ ๋ง์ ๊ฐ๋ฐ์๋ค์ด ์์ฃผ ๋ง์ฃผ์น๋ ๋ฌธ์ ๋ค๊ณผ ํด๊ฒฐ์ฑ ์ ๊ณต์ ํ ๊ฒ! ๐ง
โฐ ๋ฌธ์ 1: API ์๋ต์ด ๋๋ฌด ๋๋ ค์
โข N+1 ์ฟผ๋ฆฌ ๋ฌธ์
โข ์ธ๋ฑ์ค ์์
โข ๋คํธ์ํฌ ์ง์ฐ
โข ์๋ฒ ๋ฆฌ์์ค ๋ถ์กฑ
๐ก ํด๊ฒฐ์ฑ
1. ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ต์ ํ
- ์ฟผ๋ฆฌ ์คํ ๊ณํ ๋ถ์
- ์ ์ ํ ์ธ๋ฑ์ค ์ถ๊ฐ
- ์กฐ์ธ ์ต์ ํ
- ์บ์ฑ ๋ ์ด์ด ์ถ๊ฐ (Redis)
2. API ์๋ต ์ต์ ํ
- ํ์ด์ง๋ค์ด์ ๊ตฌํ
- ํ์ํ ํ๋๋ง ๋ฐํ
- GZIP ์์ถ ํ์ฑํ
- CDN ํ์ฉ
3. ๋น๋๊ธฐ ์ฒ๋ฆฌ
- ๋ฌด๊ฑฐ์ด ์์ ์ ๋ฐฑ๊ทธ๋ผ์ด๋ ํ๋ก
- ๊ฒฐ๊ณผ๋ ์นํ ์ด๋ ํด๋ง์ผ๋ก ์ ๋ฌ
๐ ๋ฌธ์ 2: ๋์์ฑ ๋ฌธ์ (Race Condition)
์ฌ์ฉ์๊ฐ "์ข์์" ๋ฒํผ์ ๋น ๋ฅด๊ฒ ์ฌ๋ฌ ๋ฒ ํด๋ฆญํ๋ฉด ์ค๋ณต ์์ฒญ์ด ๋ฐ์ํด. ์๋ฒ์์ ์ข์์ ์๊ฐ ์ด์ํ๊ฒ ์ฆ๊ฐํ ์ ์์ด.
๐ก ํด๊ฒฐ์ฑ
// ํด๋ผ์ด์ธํธ: ๋๋ฐ์ด์ฑ
let likeTimeout;
const handleLike = (postId) => {
clearTimeout(likeTimeout);
likeTimeout = setTimeout(() => {
apiClient.post(`/posts/${postId}/like`);
}, 300);
};
// ๋๋ ์์ฒญ ์ค๋ณต ๋ฐฉ์ง
let isLiking = false;
const handleLike = async (postId) => {
if (isLiking) return;
isLiking = true;
try {
await apiClient.post(`/posts/${postId}/like`);
} finally {
isLiking = false;
}
};
// ์๋ฒ: ๋๊ด์ ์ ๊ธ (Optimistic Locking)
UPDATE posts
SET likes_count = likes_count + 1, version = version + 1
WHERE id = ? AND version = ?
// ๋๋ ์ ๋ํฌ ์ ์ฝ์กฐ๊ฑด
CREATE UNIQUE INDEX idx_user_post_like
ON likes(user_id, post_id);
๐ ๋ฌธ์ 3: ํ ํฐ ๋ง๋ฃ ์ฒ๋ฆฌ
์ฌ์ฉ์๊ฐ ์ฑ์ ์ฌ์ฉ ์ค์ธ๋ฐ ๊ฐ์๊ธฐ ํ ํฐ์ด ๋ง๋ฃ๋ผ์ 401 ์๋ฌ๊ฐ ๋ฐ์ํด. ๋งค๋ฒ ๋ก๊ทธ์ธํ๋ผ๊ณ ํ๋ฉด ์ฌ์ฉ์ ๊ฒฝํ์ด ๋๋น ์ ธ.
๐ก ํด๊ฒฐ์ฑ : Refresh Token ํจํด
// ํ ํฐ ๊ตฌ์กฐ
{
"access_token": "...", // ์งง์ ์๋ช
(15๋ถ)
"refresh_token": "...", // ๊ธด ์๋ช
(7์ผ)
"expires_in": 900
}
// Axios ์ธํฐ์
ํฐ๋ก ์๋ ๊ฐฑ์
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
// 401 ์๋ฌ์ด๊ณ ์ฌ์๋ํ์ง ์์ ์์ฒญ์ด๋ฉด
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
try {
// Refresh Token์ผ๋ก ์ Access Token ๋ฐ๊ธ
const refreshToken = await AsyncStorage.getItem('refreshToken');
const response = await axios.post('/auth/refresh', {
refresh_token: refreshToken
});
const { access_token } = response.data;
await AsyncStorage.setItem('authToken', access_token);
// ์๋ ์์ฒญ ์ฌ์๋
originalRequest.headers.Authorization = `Bearer ${access_token}`;
return apiClient(originalRequest);
} catch (refreshError) {
// Refresh Token๋ ๋ง๋ฃ๋จ - ๋ก๊ทธ์์
await AsyncStorage.clear();
// ๋ก๊ทธ์ธ ํ๋ฉด์ผ๋ก ์ด๋
return Promise.reject(refreshError);
}
}
return Promise.reject(error);
}
);
๐ฑ ๋ฌธ์ 4: ๋ชจ๋ฐ์ผ ๋คํธ์ํฌ ๋ถ์์
์งํ์ฒ ์ด๋ ์๋ฆฌ๋ฒ ์ดํฐ์์ ๋คํธ์ํฌ๊ฐ ๋๊ฒผ๋ค ์ฐ๊ฒฐ๋๋ค ๋ฐ๋ณต๋ผ. ์ฌ์ฉ์๊ฐ ๋ต๋ตํดํด.
๐ก ํด๊ฒฐ์ฑ : ์คํ๋ผ์ธ ์ฐ์ ์ ๋ต
// 1. ๋คํธ์ํฌ ์ํ ๊ฐ์ง
import NetInfo from '@react-native-community/netinfo';
const [isOnline, setIsOnline] = useState(true);
useEffect(() => {
const unsubscribe = NetInfo.addEventListener(state => {
setIsOnline(state.isConnected);
});
return () => unsubscribe();
}, []);
// 2. ์คํ๋ผ์ธ ํ ๊ตฌํ
class OfflineQueue {
constructor() {
this.queue = [];
this.isProcessing = false;
}
async add(request) {
this.queue.push(request);
await AsyncStorage.setItem('offlineQueue', JSON.stringify(this.queue));
if (isOnline && !this.isProcessing) {
this.process();
}
}
async process() {
if (this.queue.length === 0) return;
this.isProcessing = true;
while (this.queue.length > 0) {
const request = this.queue[0];
try {
await apiClient(request);
this.queue.shift();
await AsyncStorage.setItem('offlineQueue', JSON.stringify(this.queue));
} catch (error) {
// ๋คํธ์ํฌ ์๋ฌ๋ฉด ์ค๋จ, ๋ค๋ฅธ ์๋ฌ๋ฉด ์ ๊ฑฐ
if (error.message === 'Network Error') {
break;
} else {
this.queue.shift();
}
}
}
this.isProcessing = false;
}
}
// 3. ์ฌ์ฉ ์์
const createPost = async (postData) => {
if (isOnline) {
try {
return await apiClient.post('/posts', postData);
} catch (error) {
// ๋คํธ์ํฌ ์๋ฌ๋ฉด ํ์ ์ถ๊ฐ
if (error.message === 'Network Error') {
offlineQueue.add({
method: 'post',
url: '/posts',
data: postData
});
showToast('์คํ๋ผ์ธ ์ํ์
๋๋ค. ์ฐ๊ฒฐ๋๋ฉด ์๋์ผ๋ก ์
๋ก๋๋ฉ๋๋ค.');
}
}
} else {
// ์คํ๋ผ์ธ์ด๋ฉด ๋ฐ๋ก ํ์ ์ถ๊ฐ
offlineQueue.add({
method: 'post',
url: '/posts',
data: postData
});
showToast('์คํ๋ผ์ธ ์ํ์
๋๋ค. ์ฐ๊ฒฐ๋๋ฉด ์๋์ผ๋ก ์
๋ก๋๋ฉ๋๋ค.');
}
};
๐ ๋ฌธ์ 5: API ๋ฒ์ ๊ด๋ฆฌ
API๋ฅผ ์ ๋ฐ์ดํธํ๋๋ฐ ๊ตฌ๋ฒ์ ์ฑ ์ฌ์ฉ์๋ค์ด ์๋ฌ๋ฅผ ๊ฒช์ด. ๋ชจ๋ ์ฌ์ฉ์๊ฐ ๋์์ ์ ๋ฐ์ดํธํ๋ ๊ฑด ๋ถ๊ฐ๋ฅํด.
๐ก ํด๊ฒฐ์ฑ : API ๋ฒ์ ๋ ์ ๋ต
1. URL ๋ฒ์ ๋ (๊ฐ์ฅ ๋ช ํ)
https://api.example.com/v1/users
https://api.example.com/v2/users
2. ํค๋ ๋ฒ์ ๋
GET /users
Accept: application/vnd.example.v2+json
3. ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ ๋ฒ์ ๋
GET /users?version=2
4. ํ์ ํธํ์ฑ ์ ์ง
// ์ ํ๋ ์ถ๊ฐ๋ OK
{
"id": 123,
"name": "๊น์ฌ๋ฅ",
"email": "user@example.com",
"phone": "010-1234-5678" // ์ ํ๋
}
// ๊ธฐ์กด ํ๋ ์ ๊ฑฐ๋ NG - ๋์ deprecated ํ์
{
"id": 123,
"name": "๊น์ฌ๋ฅ",
"username": "jaenung_user", // deprecated
"display_name": "๊น์ฌ๋ฅ" // ์ ํ๋
}
5. ์ ์ง์ ๋ง์ด๊ทธ๋ ์ด์
โข ์ ๋ฒ์ ์ถ์ ํ ์ต์ 6๊ฐ์์ ๊ตฌ๋ฒ์ ์ง์
โข ์ฑ ๊ฐ์ ์ ๋ฐ์ดํธ ๊ธฐ๋ฅ ๊ตฌํ
โข ์ฌ์ฉ์์๊ฒ ์ ๋ฐ์ดํธ ๊ถ์ฅ ์๋ฆผ
โข ๊ตฌ๋ฒ์ ์ฌ์ฉ๋ฅ ๋ชจ๋ํฐ๋ง
๐ ํ์ต ๋ฆฌ์์ค์ ๋ค์ ๋จ๊ณ
์ฌ๊ธฐ๊น์ง ์ฝ์๋ค๋ฉด API ํต์ ์ ๊ธฐ์ด๋ ํ์คํ ๋ค์ง ๊ฑฐ์ผ! ๐ ์ด์ ๋ ๊น์ด ๊ณต๋ถํ๊ณ ์ถ๋ค๋ฉด ์ด๋ฐ ๋ฆฌ์์ค๋ค์ ์ถ์ฒํด. ๐
๐ ์ถ์ฒ ํ์ต ์๋ฃ
โข Coursera: "APIs" by Johns Hopkins University
โข Frontend Masters: "API Design in Node.js"
โข Nomad Coders: GraphQL ๊ฐ์ (ํ๊ตญ์ด)
โข ์ธํ๋ฐ: ๋ค์ํ API ๊ฐ๋ฐ ๊ฐ์๋ค
๐ ์ฑ
โข "RESTful Web APIs" by Leonard Richardson
โข "Designing Data-Intensive Applications" by Martin Kleppmann
โข "GraphQL in Action" by Samer Buna
โข "API Security in Action" by Neil Madden
๐ ๊ณต์ ๋ฌธ์
โข REST API Tutorial: restfulapi.net
โข GraphQL ๊ณต์ ๋ฌธ์: graphql.org
โข Apollo GraphQL: apollographql.com
โข Swagger/OpenAPI: swagger.io
๐ป ์ค์ต ํ๋ซํผ
โข JSONPlaceholder: ๋ฌด๋ฃ ํ ์คํธ API
โข ReqRes: REST API ํ ์คํธ์ฉ
โข GraphQL Playground: GraphQL ์ฐ์ต
โข Postman Learning Center: API ํ ์คํธ ํ์ต
๐ ๋ค์ ๋จ๊ณ ๋ก๋๋งต
โข HTTP ํ๋กํ ์ฝ ์๋ฒฝ ์ดํด
โข REST API ์ค๊ณ ์์น ์์ง
โข JSON ๋ฐ์ดํฐ ๊ตฌ์กฐ ์ตํ๊ธฐ
โข Postman์ผ๋ก API ํ ์คํธ ์ฐ์ต
โข ๊ฐ๋จํ CRUD API ์ง์ ๋ง๋ค์ด๋ณด๊ธฐ
โข ์ธ์ฆ/์ธ๊ฐ ๊ตฌํ (JWT, OAuth)
โข ์๋ฌ ์ฒ๋ฆฌ์ ๋ก๊น
โข API ๋ฌธ์ํ (Swagger)
โข ์ฑ๋ฅ ์ต์ ํ (์บ์ฑ, ํ์ด์ง๋ค์ด์ )
โข GraphQL ๊ธฐ์ด ํ์ต
โข ์ค์ ํ๋ก์ ํธ์ ์ ์ฉ
โข ๋ง์ดํฌ๋ก์๋น์ค ์ํคํ ์ฒ
โข API ๊ฒ์ดํธ์จ์ด ๊ตฌ์ถ
โข ์ค์๊ฐ ํต์ (WebSocket, Server-Sent Events)
โข API ๋ณด์ ์ฌํ (Rate Limiting, CORS)
โข ๋ชจ๋ํฐ๋ง๊ณผ ๋ก๊น ์์คํ
โข ๋ถํ ํ ์คํธ์ ์ฑ๋ฅ ํ๋
โข ๋๊ท๋ชจ ์์คํ ์ค๊ณ
โข API ๋ฒ์ ๊ด๋ฆฌ ์ ๋ต
โข ๊ธ๋ก๋ฒ ์๋น์ค ๊ตฌ์ถ (CDN, ๋ค์ค ๋ฆฌ์ )
โข ๊ณ ๊ธ ๋ณด์ (์ ๋ก ํธ๋ฌ์คํธ, API ๊ฒ์ดํธ์จ์ด)
โข DevOps์ CI/CD ํ์ดํ๋ผ์ธ
โข ์คํ์์ค ๊ธฐ์ฌ
๐ผ ์ปค๋ฆฌ์ด ๋ฐ์
โข GitHub์ ํ๋ก์ ํธ ๊ณต๊ฐ
โข ๊ธฐ์ ๋ธ๋ก๊ทธ ์ด์
โข ์คํ์์ค ํ๋ก์ ํธ ์ฐธ์ฌ
โข ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ํ๋ฆฌ๋์ ๊ฒฝํ ์๊ธฐ
2. ๋คํธ์ํน
โข ๊ฐ๋ฐ์ ์ปค๋ฎค๋ํฐ ์ฐธ์ฌ
โข ์ปจํผ๋ฐ์ค ์ฐธ์
โข ์คํฐ๋ ๊ทธ๋ฃน ๋ง๋ค๊ธฐ
โข ๋ฉํ ์ฐพ๊ธฐ
3. ์ง์์ ํ์ต
โข ์๋ก์ด ๊ธฐ์ ํธ๋ ๋ ํ๋ก์ฐ
โข ๋ค๋ฅธ ํ์ฌ์ ๊ธฐ์ ๋ธ๋ก๊ทธ ์ฝ๊ธฐ
โข ์จ๋ผ์ธ ๊ฐ์ ์๊ฐ
โข ์ฑ ์ฝ๊ธฐ
4. ์ค๋ฌด ๊ฒฝํ
โข ์ธํด์ญ์ด๋ ์ฃผ๋์ด ํฌ์ง์ ์ง์
โข ์ฌ์ด๋ ํ๋ก์ ํธ ์งํ
โข ํด์ปคํค ์ฐธ๊ฐ
โข ํ๋ฆฌ๋์ ํ๋ก์ ํธ ์์ฃผ
๐ฌ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์! ์ฌ๊ธฐ๊น์ง ์ฝ์๋ค๋ฉด ์ ๋ง ๋๋จํด! ๐
์ฐ๋ฆฌ๋ API ํต์ ์ ๊ธฐ์ด๋ถํฐ REST API, GraphQL, ๋ณด์, ์ฑ๋ฅ ์ต์ ํ, ์ค์ ํ๋ก์ ํธ๊น์ง ์ ๋ง ๋ง์ ๋ด์ฉ์ ๋ค๋ค์ด. ์ฒ์์๋ ์ด๋ ต๊ฒ ๋๊ปด์ง ์ ์์ง๋ง, ํ๋์ฉ ์ค์ตํ๋ค ๋ณด๋ฉด ์ด๋์ ์์ฐ์ค๋ฝ๊ฒ ์ต์ํด์ง ๊ฑฐ์ผ. ๐
๊ฐ์ฅ ์ค์ํ ๊ฑด ์ง์ ๋ง๋ค์ด๋ณด๋ ๊ฑฐ์ผ! ์ด๋ก ๋ง ์๋ ๊ฒ๊ณผ ์ค์ ๋ก ๊ตฌํํด๋ณธ ๊ฒ์ ์ฒ์ง ์ฐจ์ด๊ฑฐ๋ . ์์ ํ๋ก์ ํธ๋ผ๋ ์ข์ผ๋ ์ง์ API๋ฅผ ์ค๊ณํ๊ณ ๊ตฌํํด๋ด. ์๋ฌ๋ ๋ง๋๊ณ , ๋๋ฒ๊น
๋ ํ๊ณ , ์ต์ ํ๋ ํด๋ณด๋ฉด์ ์ง์ง ์ค๋ ฅ์ด ๋์ด๋ ๊ฑฐ์ผ. ๐ช
๊ทธ๋ฆฌ๊ณ ๋งํ๋ ๋ถ๋ถ์ด ์๋ค๋ฉด ์ฃผ์ ํ์ง ๋ง๊ณ ์ง๋ฌธํด! ๊ฐ๋ฐ์ ์ปค๋ฎค๋ํฐ๋ ์๊ฐ๋ณด๋ค ์น์ ํ๊ณ , ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์ ์ ๋ฌธ๊ฐ์ ๋์์ ๋ฐ์ ์๋ ์์ด. ํผ์ ๊ณ ๋ฏผํ๋ ๊ฒ๋ณด๋ค ํจ๊ป ์ฑ์ฅํ๋ ๊ฒ ํจ์ฌ ๋น ๋ฅด๊ณ ์ฆ๊ฑฐ์. ๐ค
๋ชจ๋ฐ์ผ ์ฑ ๊ฐ๋ฐ์ ์ ๋ง ๋งค๋ ฅ์ ์ธ ๋ถ์ผ์ผ. ๋ด๊ฐ ๋ง๋ ์ฑ์ ์๋ฐฑ๋ง ๋ช
์ด ์ฌ์ฉํ๊ณ , ๊ทธ๋ค์ ์ผ์์ ํธ๋ฆฌํ๊ฒ ๋ง๋ค ์ ์๋ค๋ ๊ฑด ์ ๋ง ๋ณด๋์ฐฌ ์ผ์ด์ง. API ํต์ ์ ๊ทธ ์ค์ฌ์ ์๋ ํต์ฌ ๊ธฐ์ ์ด๊ณ ! ๐
์, ์ด์ ์ปดํจํฐ๋ฅผ ์ผ๊ณ ์ฝ๋ ์๋ํฐ๋ฅผ ์ด์ด๋ด. ์ฒซ ๋ฒ์งธ API๋ฅผ ๋ง๋ค์ด๋ณผ ์๊ฐ์ด์ผ! ํ์ดํ
! ๐ฅ
๐ ๋น์ ์ ๊ฐ๋ฐ ์ฌ์ ์ ์์ํฉ๋๋ค!
"๋ชจ๋ ์ ๋ฌธ๊ฐ๋ ์ฒ์์๋ ์ด๋ณด์์์ต๋๋ค."
ํฌ๊ธฐํ์ง ๋ง๊ณ ๊พธ์คํ ๋์๊ฐ์ธ์.
๋น์ ์ ์ฒซ API๊ฐ ์ธ์์ ๋ฐ๊ฟ ์๋ ์์ต๋๋ค! ๐
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.