Fetch Requests (reuseable layer for API client services)
Service/apiClient.js (at the same level as src folder)
class ApiClient {
constructor() {
this.baseURL = 'http://localhost:3000/api/v1';
this.defaultHeaders = {
'Content-Type': 'application/json',
Accept: 'application/json',
};
}
async customFetch(endpoint, options = {}) {
try {
const url = `\({this.baseURL}\){endpoint}`;
const response = await fetch(url, {
...options,
headers: { ...this.defaultHeaders, ...options.headers },
credentials: 'include', // Send Cookies
// 'include' on Same-origin + cross-origin
// default 'same-origin' and
// use 'omit' for public apis
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (e) {
console.log('API Error:', e);
}
}
signup(name, email, password) {
return this.customFetch('/user/signup', {
method: 'POST',
body: JSON.stringify({ name, email, password }),
});
}
}
const apiClient = new ApiClient();
export default apiClient;
uses of apiClient
// Import at the top
import apiClient from './apiClient';
// GET req
try {
const users = await apiClient.get('/users');
console.log(users);
} catch (err) {
console.error(err);
}
// POST req
const data = {
email: 'test@test.com',
password: '123456',
};
await apiClient.post('/login', data);
//PUT Req
await apiClient.put('/users/1', {
name: 'John',
});
//Delete req
await apiClient.delete('/users/1');
// Cutom header
await apiClient.get('/users', {
headers: {
'x-custom-header': 'hello',
},
});
// Query Params
const role = 'admin';
await apiClient.get(`/users?role=${role}`);
// React Example
import { useEffect, useState } from 'react';
import apiClient from './apiClient';
function Users() {
const [users, setUsers] = useState([]);
useEffect(() => {
const fetchUsers = async () => {
try {
const data = await apiClient.get('/users');
setUsers(data);
} catch (err) {
console.error(err);
}
};
fetchUsers();
}, []);
return (
<div>
{users.map(u => (
<p key={u.id}>{u.name}</p>
))}
</div>
);
}
Steps on Form Submission
- Prevent default behavior of form.
- Show loading state.
- Call apiClient.signup(...) and wait for response.
- Handle the response (e.g., redirect on success).
- Catch and log any errors.
const handleSubmit = async (e) => {
e.preventDefault();
if (!name || !email || !password) return setError('All fields required');
setLoading(true);
setError('');
try {
const data = await apiClient.signup(name, email, password);
if (data?.success) navigate('/login');
else setError(data?.message || 'Signup failed');
} catch (e) {
setError('Error occurred');
} finally {
setLoading(false);
}
};
{error && <p>{error}</p>}
<form onSubmit={handleSubmit}>
<input value={name} onChange={(e) => setName(e.target.value)} />
<input value={email} onChange={(e) => setEmail(e.target.value)} />
<input value={password} onChange={(e) => setPassword(e.target.value)} />
<button type="submit">{loading ? 'Signing up...' : 'Signup'}</button>
</form>


