feat: add axios netwoking layer with interceptors#1
Conversation
Reviewer's GuideIntroduces a centralized Axios-based HTTP layer with typed helpers, global request/response interceptors, and an auth service that consumes the new API wrapper, plus the axios dependency wiring. Sequence diagram for Axios HTTP layer with interceptorssequenceDiagram
actor FrontendComponent
participant authApi
participant api
participant http
participant RequestInterceptor
participant Backend
participant ResponseInterceptor
FrontendComponent->>authApi: login(payload)
authApi->>api: post LoginResponse /auth/login payload
api->>http: post /auth/login payload config
activate http
http->>RequestInterceptor: apply(config)
RequestInterceptor-->>http: config with Authorization header
http->>Backend: HTTP POST /auth/login
Backend-->>http: HTTP 200 ApiResponse<LoginResponse>
http->>ResponseInterceptor: handle(response)
ResponseInterceptor-->>http: response.data (LoginResponse)
http-->>api: LoginResponse
deactivate http
api-->>authApi: LoginResponse
authApi-->>FrontendComponent: LoginResponse
Sequence diagram for global error handling in Axios response interceptorsequenceDiagram
participant api
participant http
participant ResponseInterceptor
participant showToast
participant LocalStorage
api->>http: any HTTP request
http-->>api: Promise rejected with AxiosError<ApiResponse>
http->>ResponseInterceptor: handle(error)
ResponseInterceptor->>ResponseInterceptor: extract status, message
alt status is 400
ResponseInterceptor->>showToast: message error
else status is 401
ResponseInterceptor->>showToast: Session expired. Please login again. error
ResponseInterceptor->>LocalStorage: removeItem accessToken
else status is 403
ResponseInterceptor->>showToast: You do not have permission to access this resource. error
else status is 404
ResponseInterceptor->>showToast: Resource not found. error
else status is 500
ResponseInterceptor->>showToast: Internal Server Error. Please try again later. error
else other status
ResponseInterceptor->>showToast: message error
end
ResponseInterceptor-->>http: Promise.reject(error)
Class diagram for HTTP utility and auth serviceclassDiagram
class ApiResponse~T~ {
boolean success
string message
T data
}
class HttpConfig {
string baseURL
number timeout
map~string,string~ headers
}
class AxiosInstanceWrapper {
+get~T~(url string, config AxiosRequestConfig) T
+post~T~(url string, data any, config AxiosRequestConfig) T
+put~T~(url string, data any, config AxiosRequestConfig) T
+delete~T~(url string, config AxiosRequestConfig) T
+patch~T~(url string, data any, config AxiosRequestConfig) T
}
class LoginPayload {
string email
string pass
}
class UserProfile {
number id
string name
string email
string role
}
class LoginResponse {
string token
UserProfile user
}
class AuthApi {
+login(payload LoginPayload) LoginResponse
+getProfile() UserProfile
}
class ShowToast {
+showToast(message string, type string)
}
AxiosInstanceWrapper --> ApiResponse : uses as response type
AuthApi --> AxiosInstanceWrapper : uses api
AuthApi --> LoginPayload : takes
AuthApi --> LoginResponse : returns
LoginResponse --> UserProfile : contains
AxiosInstanceWrapper --> HttpConfig : created with
ShowToast <.. AxiosInstanceWrapper : used in interceptors
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey there - I've reviewed your changes - here's some feedback:
- The response interceptor returns
response.data, but theapimethods are still typed ashttp.get<T, T>etc.; consider tightening the generics (or using theApiResponse<T>type you defined) so the returned types accurately reflect the post-interceptor shape. - The
showToasthelper currently ignores the'success'case and only logs errors; either implement distinct handling for success or simplify the signature to avoid an unused parameter.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The response interceptor returns `response.data`, but the `api` methods are still typed as `http.get<T, T>` etc.; consider tightening the generics (or using the `ApiResponse<T>` type you defined) so the returned types accurately reflect the post-interceptor shape.
- The `showToast` helper currently ignores the `'success'` case and only logs errors; either implement distinct handling for success or simplify the signature to avoid an unused parameter.
## Individual Comments
### Comment 1
<location> `src/utils/http.ts:39-43` </location>
<code_context>
+ },
+);
+
+http.interceptors.response.use(
+ (response: AxiosResponse) => {
+ return response.data;
+ },
+ (error: AxiosError<ApiResponse>) => {
+ const status = error.response?.status;
+ const message = error.response?.data?.message || error.message || 'Something went wrong';
</code_context>
<issue_to_address>
**issue (bug_risk):** Align Axios generics with the actual response shape (raw data vs ApiResponse<T>).
The interceptor returns `response.data`, while `AxiosError` is `AxiosError<ApiResponse>`, implying the backend always returns an `ApiResponse<T>` envelope. But the helpers use `http.get<T, T>` / `post<T, T>` and expose `api.get<T>()` / `api.post<T>()` as if `T` were the unwrapped payload.
Consequences:
- If the server returns `ApiResponse<LoginResponse>`, `api.post<LoginResponse>()` is typed as `LoginResponse` but actually returns `ApiResponse<LoginResponse>`.
- Errors rely on `error.response?.data?.message` (i.e., `ApiResponse`), suggesting success should be typed the same way.
Consider either:
1) Making `ApiResponse<T>` the explicit return type in the helpers, or
2) Unwrapping `data` in the interceptor and updating error typing to match.
Right now, the mismatch between generics and runtime shape is likely to cause type-level bugs downstream.
</issue_to_address>
### Comment 2
<location> `src/utils/http.ts:11-13` </location>
<code_context>
+ data: T;
+};
+
+function showToast(message: string, type: 'error' | 'success' = 'error') {
+ if (type === 'error')
+ console.error(`[API Error]: ${message}`);
+}
+
</code_context>
<issue_to_address>
**suggestion:** Either handle the 'success' toast path or narrow the function signature.
`showToast` accepts `'success'` but only acts on `'error'`, so calls with `'success'` silently do nothing and can mislead callers.
You could either:
- Drop `'success'` from the type if you don’t intend to support it, or
- Implement a success branch (even a distinct console message) so `'success'` clearly has an effect.
</issue_to_address>
### Comment 3
<location> `src/utils/http.ts:25-26` </location>
<code_context>
+});
+
+http.interceptors.request.use(
+ (config) => {
+ const token = localStorage.getItem('accessToken');
+
+ if (token && config.headers) {
</code_context>
<issue_to_address>
**🚨 suggestion (security):** Consider the security trade-offs of storing the access token in localStorage.
Reading the token from `localStorage` in the interceptor makes it available to any script on the page (XSS), which may or may not fit your threat model. If CSRF is already mitigated, consider an HttpOnly cookie instead, or at least document this as a conscious trade-off. Also note this will throw outside the browser (e.g., SSR), so a guard like `typeof window !== 'undefined'` can make this more robust.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| http.interceptors.response.use( | ||
| (response: AxiosResponse) => { | ||
| return response.data; | ||
| }, | ||
| (error: AxiosError<ApiResponse>) => { |
There was a problem hiding this comment.
issue (bug_risk): Align Axios generics with the actual response shape (raw data vs ApiResponse).
The interceptor returns response.data, while AxiosError is AxiosError<ApiResponse>, implying the backend always returns an ApiResponse<T> envelope. But the helpers use http.get<T, T> / post<T, T> and expose api.get<T>() / api.post<T>() as if T were the unwrapped payload.
Consequences:
- If the server returns
ApiResponse<LoginResponse>,api.post<LoginResponse>()is typed asLoginResponsebut actually returnsApiResponse<LoginResponse>. - Errors rely on
error.response?.data?.message(i.e.,ApiResponse), suggesting success should be typed the same way.
Consider either:
- Making
ApiResponse<T>the explicit return type in the helpers, or - Unwrapping
datain the interceptor and updating error typing to match.
Right now, the mismatch between generics and runtime shape is likely to cause type-level bugs downstream.
| function showToast(message: string, type: 'error' | 'success' = 'error') { | ||
| if (type === 'error') | ||
| console.error(`[API Error]: ${message}`); |
There was a problem hiding this comment.
suggestion: Either handle the 'success' toast path or narrow the function signature.
showToast accepts 'success' but only acts on 'error', so calls with 'success' silently do nothing and can mislead callers.
You could either:
- Drop
'success'from the type if you don’t intend to support it, or - Implement a success branch (even a distinct console message) so
'success'clearly has an effect.
| (config) => { | ||
| const token = localStorage.getItem('accessToken'); |
There was a problem hiding this comment.
🚨 suggestion (security): Consider the security trade-offs of storing the access token in localStorage.
Reading the token from localStorage in the interceptor makes it available to any script on the page (XSS), which may or may not fit your threat model. If CSRF is already mitigated, consider an HttpOnly cookie instead, or at least document this as a conscious trade-off. Also note this will throw outside the browser (e.g., SSR), so a guard like typeof window !== 'undefined' can make this more robust.
Summary by Sourcery
Introduce a centralized Axios-based HTTP utility with global interceptors and wire it into new auth service APIs.
New Features:
Build: