When fetch completes, you get a Response object. It’s not the data itself — it’s a wrapper with metadata about the response and methods to extract the body.
const response = await fetch("/api/users");switch (response.status) { case 200: // OK — data returned return await response.json(); case 201: // Created — new resource created (after POST) return await response.json(); case 204: // No Content — success but no body (after DELETE) return null; case 400: // Bad Request — invalid data sent throw new Error("Invalid request data"); case 401: // Unauthorized — not logged in throw new Error("Please log in"); case 403: // Forbidden — logged in but not allowed throw new Error("Access denied"); case 404: // Not Found — resource doesn't exist throw new Error("Resource not found"); case 500: // Internal Server Error — server broke throw new Error("Server error");}
You don’t need to memorize all status codes. The important ones: 200 (success), 201 (created), 204 (no content), 400 (bad request), 401 (unauthorized), 404 (not found), 500 (server error).
The most important property. It’s true for any 2xx status code:
Copy
async function getUsers() { const response = await fetch("/api/users"); if (!response.ok) { // Status is 400, 401, 403, 404, 500, etc. throw new Error(`HTTP error: ${response.status}`); } // Status is 200, 201, etc. return response.json();}
fetch does not throw an error for 404 or 500 responses. It only throws on network failures (no internet, DNS error). You must check response.ok yourself. This is the most common source of bugs in fetch code.
The Response object has methods to read the body in different formats:
Copy
const response = await fetch("/api/users");// Most common — parse as JSONconst data = await response.json();// Plain textconst text = await response.text();// Binary data (images, files)const blob = await response.blob();// Form dataconst formData = await response.formData();
You can only read the body once. After calling .json(), you can’t call .text() on the same response. If you need the body in multiple formats, use .text() first and parse manually.
const response = await fetch("/api/users");// Get a specific headerconst contentType = response.headers.get("Content-Type");console.log(contentType); // "application/json"// Check total count (if your API sends it)const totalCount = response.headers.get("X-Total-Count");console.log(totalCount); // "42"// Iterate all headersresponse.headers.forEach((value, name) => { console.log(`${name}: ${value}`);});
FastAPI and other backends can send custom headers like X-Total-Count for pagination. Access them with response.headers.get("Header-Name").
Don’t just throw generic “request failed” errors. Parse the error response body — your FastAPI backend sends useful validation messages. Show those to your users.