๐Ÿš€ Fetch โ†’ Axios ์ „ํ™˜์œผ๋กœ ๋ฐฐ์šฐ๋Š” HTTP ํ†ต์‹ ๊ณผ RESTful API

์กฐ์ค€ํ˜•ยท2025๋…„ 10์›” 24์ผ

์ต๋ช… ๊ณ ๋ฏผํ•จ

๋ชฉ๋ก ๋ณด๊ธฐ
4/6

๐Ÿ“Œ ๊ณ„๊ธฐ

์ด๋ฒˆ์— ํŒ€์—์„œ โ€œํ”„๋กœ์ ํŠธ ์ „๋ฐ˜์˜ Fetch API๋ฅผ Axios๋กœ ๋ฆฌํŒฉํ„ฐ๋งํ•ด๋‹ฌ๋ผโ€๋Š” ์š”์ฒญ์„ ๋ฐ›์•˜๋‹ค.

๋‹จ์ˆœํžˆ ์š”์ฒญ ์ฝ”๋“œ๋ฅผ ๋ฐ”๊พธ๋Š” ์ž‘์—…์ฒ˜๋Ÿผ ๋ณด์˜€์ง€๋งŒ,

๋ง‰์ƒ ์ง„ํ–‰ํ•ด๋ณด๋‹ˆ HTTP ํ†ต์‹ ์˜ ๊ตฌ์กฐ์™€ RESTful API ์„ค๊ณ„๊นŒ์ง€

ํ•œ ๋ฒˆ์— ์ดํ•ดํ•  ์ˆ˜ ์žˆ๋Š” ๊ณ„๊ธฐ๊ฐ€ ๋๋‹ค.


๐ŸŒ 1. HTTP ํ†ต์‹  ๊ธฐ๋ณธ ๊ตฌ์กฐ ๋ณต์Šต

ํด๋ผ์ด์–ธํŠธ(๋ธŒ๋ผ์šฐ์ €)์™€ ์„œ๋ฒ„๋Š” HTTP ํ”„๋กœํ† ์ฝœ์„ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์ฃผ๊ณ ๋ฐ›๋Š”๋‹ค.

์ด๋•Œ ์š”์ฒญ์€ ๋ณดํ†ต ๋‹ค์Œ ๋„ค ๊ฐ€์ง€ ์š”์†Œ๋กœ ๊ตฌ์„ฑ๋œ๋‹ค ๐Ÿ‘‡

์š”์†Œ์„ค๋ช…์˜ˆ์‹œ
Method๋™์ž‘์˜ ์ข…๋ฅ˜ (GET, POST, PATCH, DELETE ๋“ฑ)POST /apis/posts
URL๋ฆฌ์†Œ์Šค(๋ฐ์ดํ„ฐ)์˜ ์œ„์น˜/apis/posts
Header์š”์ฒญ/์‘๋‹ต์˜ ๋ถ€๊ฐ€ ์ •๋ณดContent-Type: application/json
Body์‹ค์ œ ์ „์†ก๋˜๋Š” ๋ฐ์ดํ„ฐ{ "content": "์˜ค๋Š˜ ํ•˜๋ฃจ ๋„ˆ๋ฌด ํž˜๋“ค๋‹ค" }

๐Ÿงฉ 2. RESTful API๋ž€?

REST(Representational State Transfer) ๋Š”

์„œ๋ฒ„ ์ž์›(Resource)์„ ๋ช…ํ™•ํ•œ ๊ทœ์น™์œผ๋กœ ๋‹ค๋ฃจ๋Š” API ๋””์ž์ธ ํŒจํ„ด์ด๋‹ค.

Method์˜๋ฏธ์˜ˆ์‹œ
GET์กฐํšŒ/apis/posts
POST์ƒ์„ฑ/apis/posts
PATCH/PUT์ˆ˜์ •/apis/posts/:id
DELETE์‚ญ์ œ/apis/posts/:id

์ด ๊ทœ์น™์— ๋”ฐ๋ผ, Axios๋กœ ์š”์ฒญ์„ ๋ณด๋‚ผ ๋•Œ

axios.get, axios.post, axios.delete๊ฐ€ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ REST ๊ตฌ์กฐ๋ฅผ ํ‘œํ˜„ํ•œ๋‹ค.


โš™๏ธ 3. Axios๋ž€?

Axios๋Š” Fetch API๋ฅผ ๋” ๋‹ค๋ฃจ๊ธฐ ์‰ฝ๊ฒŒ ๊ฐ์‹ผ HTTP ํด๋ผ์ด์–ธํŠธ๋‹ค.

ํ•ญ๋ชฉFetchAxios
JSON ๋ณ€ํ™˜์ˆ˜๋™(res.json())์ž๋™(res.data)
์—๋Ÿฌ ์ฒ˜๋ฆฌif (!res.ok) ํ•„์š”200๋Œ€ ์™ธ์—” ์ž๋™ throw
์ฟ ํ‚ค ์œ ์ง€credentials: 'include'withCredentials: true
๊ธฐ๋ณธ ํ—ค๋” ์„ค์ •์ง์ ‘ ๋งค๋ฒˆ ์ง€์ •์ธ์Šคํ„ด์Šค์—์„œ ์ „์—ญ ์„ค์ • ๊ฐ€๋Šฅ
์ธํ„ฐ์…‰ํ„ฐโŒโœ… ์ง€์›

๐Ÿงฑ 4. ์ฝ”๋“œ ๋ฆฌํŒฉํ„ฐ๋ง ์˜ˆ์‹œ ๋ชจ์Œ

์•„๋ž˜๋Š” ์ด๋ฒˆ ํ”„๋กœ์ ํŠธ์—์„œ ์‹ค์ œ๋กœ ๋ฐ”๊พผ ์ฝ”๋“œ๋“ค์ด๋‹ค.

(Next.js + React Query + Zustand ๊ธฐ๋ฐ˜)


โœ… (1) ๋กœ๊ทธ์•„์›ƒ ์š”์ฒญ

Before (Fetch)

await fetch('/apis/auth/logout', { method: 'POST', credentials: 'include' })
logout()
router.push('/login')

After (Axios)

await axios.post('/apis/auth/logout', null, { withCredentials: true })
logout()
router.push('/login')

โœ” axios.post()๋Š” body๊ฐ€ ์—†์„ ๋•Œ null ์ „๋‹ฌ

โœ” ์ฟ ํ‚ค ์„ธ์…˜ ์œ ์ง€ ์‹œ withCredentials: true ํ•„์ˆ˜


โœ… (2) ๋กœ๊ทธ์ธ ์š”์ฒญ

Before

const body = new FormData()
body.append('email', email)
body.append('password', password)

const res = await fetch('/apis/auth/login', { method: 'POST', body })
const data = await res.json()
if (res.ok) setUser(data.user)

After

const body = new FormData()
body.append('email', email)
body.append('password', password)

const res = await axios.post('/apis/auth/login', body, { withCredentials: true })
setUser(res.data.user)

โœ” Axios๋Š” ์ž๋™์œผ๋กœ ์‘๋‹ต์„ JSON์œผ๋กœ ํŒŒ์‹ฑ (res.data)

โœ” 401, 500 ๋“ฑ์˜ ์ƒํƒœ ์ฝ”๋“œ๋Š” ์ž๋™์œผ๋กœ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋จ


โœ… (3) ๊ฒŒ์‹œ๊ธ€ ์ž‘์„ฑ (POST)

Before

await fetch('/apis/posts', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  credentials: 'include',
  body: JSON.stringify({ content, tags }),
})

After

await axios.post('/apis/posts', { content, tags }, { withCredentials: true })

โœ” JSON.stringify ํ•„์š” ์—†์Œ

โœ” Content-Type ์ž๋™ ์ง€์ •


โœ… (4) ๊ฒŒ์‹œ๊ธ€ ๋ชฉ๋ก ์กฐํšŒ (GET)

Before

const qs = new URLSearchParams({ cursor, limit })
const res = await fetch(`/apis/posts?${qs}`, { cache: 'no-store' })
if (!res.ok) throw new Error(`API ์‹คํŒจ: ${res.status}`)
return res.json()

After

const qs = new URLSearchParams({ cursor, limit })
const res = await axios.get(`/apis/posts?${qs}`, { withCredentials: true })
return res.data

โœ” cache๋Š” axios์—์„œ ์˜๋ฏธ ์—†์Œ โ†’ ์ƒ๋žต

โœ” 200๋Œ€ ์™ธ ์ƒํƒœ์ฝ”๋“œ๋Š” ์ž๋™์œผ๋กœ catch๋กœ ์ „๋‹ฌ


โœ… (5) ๊ฒŒ์‹œ๊ธ€ ์ƒ์„ธ ์กฐํšŒ

Before

const res = await fetch(`/apis/posts/${id}`, { cache: 'no-store' })
if (res.status === 404) throw new Error('NOT_FOUND')
return res.json()

After

try {
  const res = await axios.get(`/apis/posts/${id}`, { withCredentials: true })
  return res.data
} catch (e) {
  if (axios.isAxiosError(e) && e.response?.status === 404) {
    throw new Error('NOT_FOUND')
  }
  throw e
}

โœ” 404 ์˜ˆ์™ธ๋Š” ๋ช…์‹œ์ ์œผ๋กœ ์ฒ˜๋ฆฌ ๊ฐ€๋Šฅ


โœ… (6) ๋งˆ์ดํŽ˜์ด์ง€ ๋‚ด ๊ฒŒ์‹œ๊ธ€ ์‚ญ์ œ

Before

await fetch(`/apis/me/posts/${id}`, { method: 'DELETE' })

After

await axios.delete(`/apis/me/posts/${id}`, { withCredentials: true })

โœ” RESTful ๊ทœ์น™์— ๋งž๋Š” ๋ฉ”์„œ๋“œ ์ด๋ฆ„(delete)

โœ” ์—๋Ÿฌ ๋ฐœ์ƒ ์‹œ catch์—์„œ ๊ฐ„๋‹จํžˆ ์ฒ˜๋ฆฌ


๐Ÿ“š 5. HTTP ๋ฉ”์„œ๋“œ์™€ Body ๊ตฌ์กฐ ์ •๋ฆฌ

MethodBody ์‚ฌ์šฉ ์—ฌ๋ถ€๋ชฉ์ ์˜ˆ์‹œ
GETโŒ ์—†์Œ๋ฐ์ดํ„ฐ ์กฐํšŒ/apis/posts
POSTโœ… ์žˆ์Œ์ƒˆ ๋ฐ์ดํ„ฐ ์ƒ์„ฑ/apis/posts
PATCH/PUTโœ… ์žˆ์Œ์ผ๋ถ€ ์ˆ˜์ •/apis/posts/:id
DELETEโŒ ์—†์Œ๋ฐ์ดํ„ฐ ์‚ญ์ œ/apis/posts/:id

๐Ÿ’ก POST๋งŒ body๋ฅผ ๋ฐ˜๋“œ์‹œ ํฌํ•จํ•ด์•ผ ํ•˜๊ณ ,

GET์ด๋‚˜ DELETE๋Š” URL๋กœ ๋ฆฌ์†Œ์Šค๋ฅผ ์ง€์ •ํ•˜๋Š” ๊ฒŒ ์›์น™์ด๋‹ค.


๐Ÿง  6. Axios๋กœ ๋ฐฐ์šฐ๋Š” HTTP ํ†ต์‹ ์˜ ๋ณธ์งˆ

Axios๋Š” ๋‹จ์ˆœํžˆ ๋ฌธ๋ฒ•์„ ๊ฐ„๊ฒฐํ•˜๊ฒŒ ๋งŒ๋“œ๋Š” ๋„๊ตฌ๊ฐ€ ์•„๋‹ˆ๋ผ,

HTTP ์š”์ฒญ์˜ ๊ตฌ์กฐ๋ฅผ ๋ช…ํ™•ํ•˜๊ฒŒ ๋“œ๋Ÿฌ๋‚ด๋Š” ๋„๊ตฌ์˜€๋‹ค.

axios.post() โ†’ โ€œ์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ธ๋‹คโ€axios.get() โ†’ โ€œ์„œ๋ฒ„์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹คโ€axios.delete() โ†’ โ€œ์„œ๋ฒ„์˜ ๋ฆฌ์†Œ์Šค๋ฅผ ์ œ๊ฑฐํ•œ๋‹คโ€

์ด๋ ‡๊ฒŒ HTTP์˜ ์˜๋ฏธ๊ฐ€ ์ฝ”๋“œ์— ๋…น์•„๋“ ๋‹ค.


โš™๏ธ 7. ๋ฆฌํŒฉํ„ฐ๋ง ํ›„ ์žฅ์  ์š”์•ฝ

ํ•ญ๋ชฉFetchAxios
์ฝ”๋“œ ๊ธธ์ด๋ฐ˜๋ณต ๋งŽ์Œ์งง๊ณ  ๊ฐ„๊ฒฐ
์—๋Ÿฌ ์ฒ˜๋ฆฌif ๋ฌธ ์ˆ˜๋™catch ์ž๋™
๊ณตํ†ต ์„ค์ •๋ถˆ๊ฐ€์ธ์Šคํ„ด์Šค์—์„œ ๊ฐ€๋Šฅ
์œ ์ง€๋ณด์ˆ˜์„ฑ๋‚ฎ์Œ๋†’์Œ
REST ํ‘œํ˜„์ œํ•œ์ ์ง๊ด€์  (get/post/delete)

๐Ÿงญ 8. ์ด๋ฒˆ ๋ฆฌํŒฉํ„ฐ๋ง์œผ๋กœ ๋ฐฐ์šด ์ 

  1. Axios๋Š” fetch์˜ ๋Œ€์ฒด์ œ๊ฐ€ ์•„๋‹ˆ๋ผ ์ƒ์œ„ ์ถ”์ƒํ™” ๋„๊ตฌ์˜€๋‹ค.
  2. RESTful ๊ตฌ์กฐ์˜ ์˜๋ฏธ๋ฅผ ์ฝ”๋“œ๋กœ ์‹ค๊ฐํ–ˆ๋‹ค.
    • POST์—” body๊ฐ€ ๊ผญ ์žˆ์–ด์•ผ ํ•˜๊ณ ,
    • GET์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค.
  3. HTTP ํ†ต์‹ ์˜ ํ๋ฆ„ โ€” ์š”์ฒญ/์‘๋‹ต/์ƒํƒœ ์ฝ”๋“œ์˜ ์—ญํ• ์„ ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ดํ•ดํ•˜๊ฒŒ ๋๋‹ค.
  4. ์ฝ”๋“œ์˜ ์ผ๊ด€์„ฑ๊ณผ ๊ฐ€๋…์„ฑ์ด ํฌ๊ฒŒ ์ข‹์•„์กŒ๋‹ค.
profile
์ฝ”๋ฆฐ์ด

0๊ฐœ์˜ ๋Œ“๊ธ€