Skip to content

feat: add axios netwoking layer with interceptors#1

Merged
suradet-ps merged 1 commit intomainfrom
feat/networking-layer
Dec 7, 2025
Merged

feat: add axios netwoking layer with interceptors#1
suradet-ps merged 1 commit intomainfrom
feat/networking-layer

Conversation

@suradet-ps
Copy link
Copy Markdown
Owner

@suradet-ps suradet-ps commented Dec 7, 2025

  • Added axios dependency
  • Created src/utils/http.ts wrapper
  • Added request/response interceptors

Summary by Sourcery

Introduce a centralized Axios-based HTTP utility with global interceptors and wire it into new auth service APIs.

New Features:

  • Add an Axios-backed HTTP client wrapper with request and response interceptors for authentication and error handling.
  • Expose a typed auth service with login and profile retrieval endpoints built on the shared HTTP client.

Build:

  • Add Axios as a runtime dependency for HTTP networking.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai Bot commented Dec 7, 2025

Reviewer's Guide

Introduces 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 interceptors

sequenceDiagram
  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
Loading

Sequence diagram for global error handling in Axios response interceptor

sequenceDiagram
  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)
Loading

Class diagram for HTTP utility and auth service

classDiagram
  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
Loading

File-Level Changes

Change Details Files
Introduce a shared Axios HTTP client with token-based auth and global error handling.
  • Create a configured Axios instance with base URL, timeout, and JSON headers.
  • Add a request interceptor that injects a Bearer token from localStorage when present.
  • Add a response interceptor that unwraps response.data and centralizes error handling by status code, including clearing tokens on 401.
  • Provide a lightweight showToast helper (currently console.error) for surfacing errors.
  • Export a typed api helper with generic get/post/put/delete/patch wrappers returning unwrapped response payloads.
src/utils/http.ts
Add an authentication service built on the shared HTTP client.
  • Define TypeScript types for login payload, user profile, and login responses.
  • Implement authApi.login using the shared api.post helper to call /auth/login.
  • Implement authApi.getProfile using the shared api.get helper to call /auth/me.
src/services/auth.ts
Wire axios into the project as a runtime dependency.
  • Add axios to package.json dependencies and lockfile.
package.json
pnpm-lock.yaml

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey there - I've reviewed your changes - here's some feedback:

  • 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.
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>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment thread src/utils/http.ts
Comment on lines +39 to +43
http.interceptors.response.use(
(response: AxiosResponse) => {
return response.data;
},
(error: AxiosError<ApiResponse>) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 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.

Comment thread src/utils/http.ts
Comment on lines +11 to +13
function showToast(message: string, type: 'error' | 'success' = 'error') {
if (type === 'error')
console.error(`[API Error]: ${message}`);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment thread src/utils/http.ts
Comment on lines +25 to +26
(config) => {
const token = localStorage.getItem('accessToken');
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 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.

@suradet-ps suradet-ps merged commit 715b209 into main Dec 7, 2025
3 checks passed
@suradet-ps suradet-ps deleted the feat/networking-layer branch December 7, 2025 05:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant