Fetch and axios in React
React doesn’t have built-in HTTP functionality — you use either the browser’s fetch API or a library like axios. This lesson shows both and explains why axios is the better choice for Bulletin.
The browser’s fetch API
Section titled “The browser’s fetch API”// Using native fetchconst response = await fetch('http://localhost:3000/posts')const data = await response.json()fetch works, but has some rough edges:
- Always check
response.ok— fetch doesn’t throw on 4xx/5xx responses - You must call
.json()manually - No automatic request/response transformation
- No interceptors (for adding auth headers globally)
// The full fetch pattern with proper error handlingconst response = await fetch('http://localhost:3000/posts')if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`)}const data = await response.json()axios is a popular HTTP client that solves fetch’s pain points:
npm install axiosimport axios from 'axios'
// GET requestconst { data } = await axios.get('http://localhost:3000/posts')// data is already parsed JSON
// POST requestconst { data: newPost } = await axios.post('http://localhost:3000/posts', { title: 'Hello', body: 'World',})Key differences from fetch:
- Throws on 4xx/5xx — you get proper error handling with try/catch
- Automatic JSON — parses responses and serializes request bodies
- Interceptors — add headers to all requests globally (used for auth tokens)
- TypeScript generics —
axios.get<Post[]>('/posts')types the response
Using axios in a component
Section titled “Using axios in a component”import { useState, useEffect } from 'react'import axios from 'axios'import type { Post } from '../types/api'
function PostFeed() { const [posts, setPosts] = useState<Post[]>([]) const [loading, setLoading] = useState(true) const [error, setError] = useState<string | null>(null)
useEffect(() => { axios.get<{ posts: Post[] }>('http://localhost:3000/posts') .then(({ data }) => { setPosts(data.posts) setLoading(false) }) .catch((err) => { setError(err.response?.data?.error || 'Failed to load posts') setLoading(false) }) }, [])
if (loading) return <p>Loading...</p> if (error) return <p>Error: {error}</p>
return ( <ul> {posts.map(post => ( <li key={post.id}>{post.title}</li> ))} </ul> )}Handling axios errors
Section titled “Handling axios errors”When the API returns a 4xx or 5xx, axios throws. The error object has a response property:
try { const { data } = await axios.post('/auth/login', credentials)} catch (err) { if (axios.isAxiosError(err)) { const message = err.response?.data?.error || 'Unknown error' const status = err.response?.status || 0 console.log(`${status}: ${message}`) }}axios.isAxiosError(err) is a type guard that gives you access to the response properties.
Exercise
Section titled “Exercise”- Install axios in your
bulletinfrontend project. - Create a simple component that fetches from
GET /postson mount. - Display the posts in a list, with a loading state and error state.
- Test with the API running locally (or your Railway URL).
fetchworks but requires manual.json()parsing andresponse.okchecking.- axios automatically parses JSON, throws on errors, and supports interceptors.
- Always handle all three states: loading, success, error.
axios.isAxiosError(err)is the type guard for accessing error response data.