Every web app needs to communicate with a backend. JavaScript’s fetch() function handles HTTP requests - GET to retrieve data, POST to create data, PUT to update, DELETE to remove.You’ll use this pattern in almost every React component that needs backend data.
async function getUsers() { try { const response = await fetch('http://localhost:8000/api/users'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const data = await response.json(); return data; } catch (error) { console.error('Failed to fetch users:', error); throw error; // Re-throw so caller can handle it }}
Always check response.ok before parsing. A 404 or 500 status won’t throw an error automatically - you have to check for it.
fetch() only throws on network errors (no internet, DNS failure, etc.), not on HTTP error status codes. A 404 or 500 response is considered a “successful” fetch. Always check response.ok.
// Update a userasync function updateUser(userId, updates) { const response = await fetch(`http://localhost:8000/api/users/${userId}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(updates), }); if (!response.ok) throw new Error('Failed to update user'); return response.json();}// Delete a userasync function deleteUser(userId) { const response = await fetch(`http://localhost:8000/api/users/${userId}`, { method: 'DELETE', }); if (!response.ok) throw new Error('Failed to delete user'); // DELETE often returns no content, so check first if (response.status === 204) return null; return response.json();}
PUT and DELETE follow the same pattern. DELETE requests usually don’t have a body, and often return status 204 (No Content) instead of JSON.
// ❌ Don't hardcode your API URLconst response = await fetch('http://localhost:8000/api/users');// ✅ Use an environment variableconst API_URL = import.meta.env.VITE_API_URL;const response = await fetch(`${API_URL}/api/users`);
Store your API URL in a .env file so you can change it between development and production without modifying code.
In Vite (the build tool we’ll use), environment variables must start with VITE_ to be exposed to your code. In your .env file: VITE_API_URL=http://localhost:8000
// ❌ Wrong: Missing awaitasync function getUsers() { const data = fetch('http://localhost:8000/api/users'); console.log(data); // Promise { <pending> } return data;}// ✅ Correct: Use awaitasync function getUsers() { const response = await fetch('http://localhost:8000/api/users'); const data = await response.json(); return data;}
Without await, you get a Promise object, not the data. This is one of the most common async mistakes. Remember: fetch() returns a Promise, and so does .json().
Not checking response.ok
Copy
// ❌ Wrong: Assumes request succeededasync function getUsers() { const response = await fetch('http://localhost:8000/api/users'); return response.json(); // Might fail on 404/500}// ✅ Correct: Check for errorsasync function getUsers() { const response = await fetch('http://localhost:8000/api/users'); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json();}
A 404 or 500 response won’t throw an error automatically. Always check response.ok before calling .json(). Otherwise you might try to parse an error page as JSON and get confusing errors.
The body must be a string, not a JavaScript object. Always use JSON.stringify(), and don’t forget the Content-Type header.
Not using try/catch
Copy
// ❌ Wrong: No error handlingasync function getUsers() { const response = await fetch('http://localhost:8000/api/users'); return response.json();}// ✅ Correct: Wrap in try/catchasync function getUsers() { try { const response = await fetch('http://localhost:8000/api/users'); if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); return await response.json(); } catch (error) { console.error('Failed to fetch users:', error); throw error; }}
Network requests can fail for many reasons - no internet, server down, CORS errors. Always wrap fetch calls in try/catch so you can handle errors gracefully.
You can now fetch data from your FastAPI backend. Next, let’s learn how to work with the responses you get back — status codes, headers, and different response formats.