logicloom-nextjs-starter
Version:
A production-ready Next.js starter template with authentication, i18n, dark mode, and modern patterns
157 lines (138 loc) • 4.43 kB
text/typescript
import axios from "axios";
import { storage } from "./storage";
const axiosInstance = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_URL || "/api",
timeout: 10000,
headers: {
"Content-Type": "application/json",
},
withCredentials: true,
});
let isRefreshing = false;
let failedQueue: Array<{
resolve: (value?: any) => void;
reject: (reason?: any) => void;
}> = [];
const processQueue = (error: any, token: string | null = null) => {
failedQueue.forEach((prom) => {
if (error) {
prom.reject(error);
} else {
prom.resolve(token);
}
});
failedQueue = [];
};
// Request interceptor
axiosInstance.interceptors.request.use(
(config) => {
const token = storage.getToken();
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => {
return Promise.reject(error);
}
);
// Response interceptor
axiosInstance.interceptors.response.use(
(response) => {
return response;
},
async (error) => {
const originalRequest = error.config;
// Handle 401 Unauthorized
if (error.response?.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
// If already refreshing, queue this request
return new Promise((resolve, reject) => {
failedQueue.push({ resolve, reject });
})
.then((token) => {
originalRequest.headers.Authorization = `Bearer ${token}`;
return axiosInstance(originalRequest);
})
.catch((err) => {
return Promise.reject(err);
});
}
originalRequest._retry = true;
isRefreshing = true;
try {
// Try to refresh token
const response = await fetch("/api/auth/refresh", {
method: "POST",
credentials: "include", // Important: send cookies
});
if (response.ok) {
const data = await response.json();
const newToken = data.token;
// Update token in storage
storage.setToken(newToken);
// Process queued requests
processQueue(null, newToken);
// Retry original request
originalRequest.headers.Authorization = `Bearer ${newToken}`;
return axiosInstance(originalRequest);
} else {
// Refresh failed, logout user
processQueue(new Error("Token refresh failed"), null);
storage.clearAuth();
if (typeof window !== "undefined") {
window.location.href = "/auth";
}
return Promise.reject(error);
}
} catch (refreshError) {
processQueue(refreshError, null);
storage.clearAuth();
if (typeof window !== "undefined") {
window.location.href = "/auth";
}
return Promise.reject(refreshError);
} finally {
isRefreshing = false;
}
}
// Handle other errors with detailed information
if (error.response) {
// Server responded with error
const status = error.response.status;
const data = error.response.data;
const message = data?.message || `Request failed with status ${status}`;
try {
console.error("API Error:", {
url: error.config?.url || "unknown",
method: error.config?.method || "unknown",
status,
message,
data: data ? JSON.stringify(data) : "no data",
});
} catch (logError) {
console.error("API Error occurred, but could not log details");
}
return Promise.reject(error);
} else if (error.request) {
// Request made but no response
try {
console.error("Network Error:", {
url: error.config?.url || "unknown",
method: error.config?.method || "unknown",
message: "No response from server",
});
} catch (logError) {
console.error("Network Error occurred");
}
return Promise.reject(
new Error("No response from server. Please check your connection.")
);
} else {
// Something else happened
console.error("Request Error:", error.message || "Unknown error");
return Promise.reject(error);
}
}
);
export default axiosInstance;