JavaScript Fetch API Explained — GET, POST, PUT, DELETE & CRUD with REST
The Fetch API is the modern JavaScript interface for making HTTP requests, replacing the older XMLHttpRequest. It’s built into every modern browser and provides a clean, promise-based way to communicate with servers. The Doda Browser uses Fetch for everything from loading web pages to syncing bookmarks, and the Durga Antivirus Pro dashboard uses it to fetch threat data and send scan results.
What You’ll Learn
- Making GET, POST, PUT, and DELETE requests with Fetch
- Setting headers, sending JSON, and handling responses
- Uploading files with FormData
- Cancelling requests with AbortController
- Error handling — network errors vs HTTP errors
- Building request interceptors for logging and auth
- A complete CRUD example against a REST API
Why Fetch Matters
Every modern web application communicates with a server. Whether you’re loading user data, submitting a form, or uploading a file, you need a reliable way to make HTTP requests. Fetch is simpler, more powerful, and more consistent than the old XMLHttpRequest — and it integrates naturally with promises and async/await.
Learning Path
flowchart LR
A[Async JavaScript] --> B[Fetch API]
B --> C[Promises Deep Dive]
B --> D[Browser Storage]
B --> E[You Are Here]
Basic GET Request
The simplest Fetch call — a GET request to retrieve data:
async function getUsers() {
try {
const response = await fetch('https://api.example.com/users');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const users = await response.json();
console.log(users);
return users;
} catch (error) {
console.error('Failed to fetch users:', error);
return [];
}
}
getUsers();Critical: Fetch only rejects on network errors (DNS failure, no connection, etc.). HTTP errors like 404 or 500 are treated as successful responses. Always check response.ok.
POST — Sending Data
async function createUser(userData) {
const response = await fetch('https://api.example.com/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${getToken()}`
},
body: JSON.stringify(userData)
});
if (!response.ok) {
const error = await response.json().catch(() => ({}));
throw new Error(error.message || `HTTP ${response.status}`);
}
return response.json();
}
const newUser = await createUser({
name: 'Alice',
email: 'alice@example.com'
});PUT — Updating Data
async function updateUser(id, updates) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(updates)
});
if (!response.ok) throw new Error(`Update failed: ${response.status}`);
return response.json();
}DELETE — Removing Data
async function deleteUser(id) {
const response = await fetch(`https://api.example.com/users/${id}`, {
method: 'DELETE'
});
if (!response.ok) throw new Error(`Delete failed: ${response.status}`);
// 204 No Content — no body to parse
if (response.status === 204) return { success: true };
return response.json();
}Working with Headers
The Headers API gives you full control over request and response headers:
// Create headers
const headers = new Headers();
headers.set('Content-Type', 'application/json');
headers.set('Authorization', `Bearer ${token}`);
headers.set('X-Request-ID', crypto.randomUUID());
// Headers are iterable
for (const [name, value] of headers) {
console.log(`${name}: ${value}`);
}
// Use with fetch
const response = await fetch('/api/data', { headers });
console.log(response.headers.get('X-RateLimit-Remaining'));File Upload with FormData
async function uploadFile(file) {
const formData = new FormData();
formData.append('file', file);
formData.append('description', 'User avatar');
const response = await fetch('/api/upload', {
method: 'POST',
// Don't set Content-Type — browser sets it with boundary
body: formData
});
if (!response.ok) throw new Error('Upload failed');
return response.json();
}
// Usage
const fileInput = document.querySelector('input[type="file"]');
fileInput.addEventListener('change', async (e) => {
const file = e.target.files[0];
const result = await uploadFile(file);
console.log('Uploaded:', result.url);
});Note: When sending FormData, don’t set Content-Type manually. The browser sets it automatically with the correct multipart/form-data boundary.
AbortController — Cancelling Requests
function fetchWithTimeout(url, timeoutMs = 5000) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), timeoutMs);
return fetch(url, { signal: controller.signal })
.finally(() => clearTimeout(timeout));
}
// Usage
try {
const data = await fetchWithTimeout('https://api.example.com/data', 3000);
console.log(data);
} catch (error) {
if (error.name === 'AbortError') {
console.error('Request timed out');
} else {
console.error('Request failed:', error);
}
}
// Cancelling on user action
const controller = new AbortController();
document.getElementById('cancelBtn').onclick = () => controller.abort();
fetch('/api/large-data', { signal: controller.signal })
.then(r => r.json())
.catch(err => {
if (err.name === 'AbortError') {
console.log('Request cancelled by user');
}
});Error Handling Patterns
class ApiError extends Error {
constructor(status, message, data) {
super(message);
this.name = 'ApiError';
this.status = status;
this.data = data;
}
}
async function apiRequest(url, options = {}) {
try {
const response = await fetch(url, {
headers: {
'Content-Type': 'application/json',
...options.headers
},
...options
});
// Handle non-JSON responses
const contentType = response.headers.get('content-type');
const isJson = contentType?.includes('application/json');
const data = isJson ? await response.json() : null;
if (!response.ok) {
throw new ApiError(
response.status,
data?.message || `HTTP ${response.status}`,
data
);
}
return data;
} catch (error) {
if (error instanceof ApiError) throw error;
throw new ApiError(0, 'Network error — check your connection');
}
}Request Interceptors
Interceptors let you modify requests or responses globally — perfect for adding auth tokens, logging, or retry logic:
function createApiClient(baseURL) {
const interceptors = [];
function use(interceptor) {
interceptors.push(interceptor);
}
async function request(endpoint, options = {}) {
let config = {
url: `${baseURL}${endpoint}`,
...options,
headers: { 'Content-Type': 'application/json', ...options.headers }
};
// Run request interceptors
for (const interceptor of interceptors) {
if (interceptor.request) {
config = await interceptor.request(config);
}
}
try {
let response = await fetch(config.url, config);
// Run response interceptors
for (const interceptor of interceptors) {
if (interceptor.response) {
response = await interceptor.response(response);
}
}
return response.json();
} catch (error) {
throw error;
}
}
return { use, request };
}
// Usage
const api = createApiClient('https://api.example.com');
api.use({
request: async (config) => {
config.headers.Authorization = `Bearer ${getToken()}`;
console.log(`[${config.method}] ${config.url}`);
return config;
},
response: async (response) => {
if (response.status === 401) {
// Redirect to login
window.location.href = '/login';
}
return response;
}
});
const users = await api.request('/users');Full CRUD Example
class UserService {
constructor(baseURL = 'https://api.example.com/users') {
this.baseURL = baseURL;
}
async getAll() {
return apiRequest(this.baseURL);
}
async getById(id) {
return apiRequest(`${this.baseURL}/${id}`);
}
async create(userData) {
return apiRequest(this.baseURL, {
method: 'POST',
body: JSON.stringify(userData)
});
}
async update(id, updates) {
return apiRequest(`${this.baseURL}/${id}`, {
method: 'PUT',
body: JSON.stringify(updates)
});
}
async delete(id) {
return apiRequest(`${this.baseURL}/${id}`, {
method: 'DELETE'
});
}
}
const users = new UserService();
await users.create({ name: 'Bob', email: 'bob@example.com' });
const allUsers = await users.getAll();Common Mistakes
1. Not checking response.ok
const res = await fetch('/api/data');
const data = await res.json(); // Throws if 404 returns HTML
Fix: Always check response.ok before parsing.
2. Forgetting to parse JSON
const data = await fetch('/api/data'); // Returns Response object
Fix: Call response.json() to parse the body.
3. CORS errors
Making requests to a different origin without proper CORS headers. Fix: Ensure the server sends Access-Control-Allow-Origin headers.
4. Not handling network errors
const data = await fetch('/api/data').then(r => r.json());
// If offline, this throws an unhandled rejection
Fix: Always wrap in try/catch.
5. Setting Content-Type with FormData
fetch('/api/upload', {
method: 'POST',
headers: { 'Content-Type': 'application/json' }, // ❌
body: formData
});Fix: Let the browser set the content type for FormData.
Practice Questions
What’s the difference between a network error and an HTTP error in Fetch? Network errors (DNS failure, no connection) cause Fetch to reject. HTTP errors (404, 500) are treated as successful responses — you must check
response.ok.How do you cancel a fetch request? Use an
AbortController. Pass itssignalto the request and callcontroller.abort()to cancel.Why shouldn’t you set Content-Type for FormData? The browser needs to set the correct
multipart/form-databoundary. Setting it manually breaks the upload.What does
response.json()return? A Promise that resolves to the parsed JSON body. You mustawaitit or use.then().
Challenge: Build a function fetchWithRetry(url, options, maxRetries) that automatically retries failed requests with exponential backoff.
Mini Project: REST API Client
async function createPost() {
const newPost = {
title: 'My New Post',
body: 'This is the content of my post.',
userId: 1
};
const response = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
body: JSON.stringify(newPost),
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
});
const post = await response.json();
console.log('Created post:', post);
// Output: Created post: { id: 101, title: 'My New Post', ... }
}FAQ
What’s Next
| Lesson | Description |
|---|---|
| JavaScript Home | Back to the JavaScript hub |
| https://tutorials.dodatech.com/programming-languages/javascript/js-async/ | Async JavaScript fundamentals |
| https://tutorials.dodatech.com/programming-languages/javascript/js-promises-deep/ | Promises deep dive |
| https://tutorials.dodatech.com/programming-languages/javascript/js-error-handling/ | Error handling patterns |
| REST API | RESTful API design |
Built by the developers of Doda Browser, DodaZIP, and Durga Antivirus Pro.
What’s Next
Congratulations on completing this JavaScript Fetch API tutorial! Here’s where to go from here:
- Practice daily — Consistency is more important than long study sessions
- Build a project — Apply what you learned by building something real
- Explore related topics — Check out other tutorials in the same category
- Join the community — Discuss with other learners and share your progress
Remember: every expert was once a beginner. Keep coding!
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro