Skip to content

feat!: replace axios with native fetch#366

Open
rhamzeh wants to merge 9 commits intomainfrom
feat/drop-axios
Open

feat!: replace axios with native fetch#366
rhamzeh wants to merge 9 commits intomainfrom
feat/drop-axios

Conversation

@rhamzeh
Copy link
Copy Markdown
Member

@rhamzeh rhamzeh commented Mar 31, 2026

The global fetch API is built into Node 20+, browsers, Deno, Cloudflare Workers, and Vercel Edge.

On modern versions of node that use undici internally, this should result in improved performance.

Warning

BREAKING CHANGE: The $response property type changes from AxiosResponse<T> to FgaResponse<T>. The constructor now accepts an optional HttpClient instead of AxiosInstance.
baseOptions.httpAgent/httpsAgent are no longer applicable as fetch handles connection pooling natively.

Description

What problem is being solved?

How is it being solved?

What changes are made to solve it?

References

closes #18
unblocks #17

Review Checklist

  • I have clicked on "allow edits by maintainers".
  • I have added documentation for new/changed functionality in this PR or in a PR to openfga.dev [Provide a link to any relevant PRs in the references section above]
  • The correct base branch is being used, if not main
  • I have added tests to validate that the change in functionality is working as expected

Summary by CodeRabbit

  • Breaking Changes

    • Replaced axios with native fetch-based HTTP layer; $response now uses FgaResponse and injection uses HttpClient instead of previous axios instance. Public constructors/signatures updated to accept HttpClient.
  • New Features

    • Custom HTTP client support (custom fetch, default headers, default timeout).
    • Exposed FgaResponse, HttpClient and related types.
  • Documentation

    • README and runtime docs updated with Custom HTTP Client and streaming/ReadableStream guidance.
  • Chores

    • RequestBuilderOptions now typed with timeout and retry params.

rhamzeh added 3 commits March 31, 2026 09:01
The global fetch API is built into Node 20+, browsers, Deno, Cloudflare
Workers, and Vercel Edge.

On modern versions of node taht use undici internally, this should result
in improved perfomance.

BREAKING CHANGE: The `$response` property type changes from
`AxiosResponse<T>` to `FgaResponse<T>`. The constructor
now accepts an optional `HttpClient` instead of `AxiosInstance`.
`baseOptions.httpAgent`/`httpsAgent` are no longer applicable as fetch
handles connection pooling natively.
@rhamzeh rhamzeh requested a review from a team as a code owner March 31, 2026 22:58
Copilot AI review requested due to automatic review settings March 31, 2026 22:58
@rhamzeh rhamzeh requested review from a team as code owners March 31, 2026 22:58
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 31, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3d492658-37e6-43b9-a9ec-b4d1b0e145a6

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

This PR replaces axios with a fetch-based HttpClient across the SDK, introducing FgaResponse<T> and HttpClient, changing injected/constructed client types (AxiosInstance → HttpClient), updating request/timeout/retry plumbing to use AbortSignal/fetch, and updating docs/tests accordingly.

Changes

Cohort / File(s) Summary
Documentation
CHANGELOG.md, README.md, SUPPORTED_RUNTIMES.md
Documented axios→fetch migration, new HttpClient interface and FgaResponse shape, added "Custom HTTP Client" docs and runtime support updates.
HTTP Abstraction Layer
src/common.ts
Introduced FgaResponse<T>, HttpClient, globalHttpClient; replaced axios request/response/error handling with fetch-based attemptHttpRequest, header normalization, timeout via AbortSignal.timeout, and retry/error mapping using HttpErrorContext. Updated request factories and RequestBuilderOptions.
API Layer
src/api.ts
Replaced AxiosInstance parameters with HttpClient across request builders and factory; typed options as RequestBuilderOptions; wired globalHttpClient into execution.
Core Client
src/base.ts, src/client.ts, src/configuration.ts
BaseAPI and OpenFgaClient now accept httpClient?: HttpClient; BaseAPI stores protected httpClient; default fetch-based client created when none provided; BaseOptions exported/optional header typing updated.
Error Handling
src/errors.ts
Added HttpErrorContext; refactored FgaApiError and subclasses to accept `HttpErrorContext
Authentication & Credentials
src/credentials/credentials.ts
Credentials.init and constructor changed to accept httpClient?: HttpClient; token refresh flow uses fetch-based request wiring; safe header access updated.
Public Exports
src/index.ts
Re-exported FgaResponse, HttpClient, CallResult, and PromiseResult.
Dependencies
package.json
Removed axios dependency.
Tests
tests/fetch-http-client.test.ts, tests/errors-authentication.test.ts, tests/index.test.ts, tests/errors.test.ts
Added comprehensive fetch-http-client tests covering parsing, timeout, retries, error mapping, header merging, streaming, and injection; updated existing tests to use HttpErrorContext and httpClient.fetch mocking.

Sequence Diagram

sequenceDiagram
    participant Client as OpenFgaClient
    participant API as OpenFgaApi / executeApiRequest
    participant HC as HttpClient
    participant Fetch as globalThis.fetch
    participant Retry as RetryLogic

    Client->>API: call API method (options, httpClient?)
    API->>HC: resolve injected or globalHttpClient
    API->>Retry: execute attemptHttpRequest(config, hc)
    Retry->>HC: merge headers, build FetchRequestConfig (method, body, timeout, responseType)
    HC->>Fetch: fetch(url, {method, headers, body, signal})
    alt Success 2xx
        Fetch->>HC: Response
        HC->>API: parse JSON/text or return stream -> FgaResponse{status,statusText,headers,data}
        API->>Client: resolve CallResult with $response: FgaResponse
    else Retryable (429/5xx/network)
        Fetch->>Retry: Error/status
        Retry->>Retry: backoff and retry attempts
        Retry->>HC: re-attempt fetch
    else Non-retryable (4xx, auth, not-found)
        Fetch->>HC: Response
        HC->>API: build HttpErrorContext and throw typed FgaApiError subclass
        API->>Client: reject with error
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • SoulPancake
  • ewanharris
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.27% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat!: replace axios with native fetch' clearly and accurately summarizes the primary breaking change in the PR, matching the core objective to replace axios dependency with the native fetch API.
Linked Issues check ✅ Passed The PR fulfills issue #18's core requirements: replaces axios with a cross-platform HTTP solution (native fetch), supports Node.js 18+, has no new dependencies, is promise-based, includes streaming support, and now supports Deno/Cloudflare Workers/Vercel Edge through cross-platform fetch compatibility.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the axios-to-fetch migration objective: HTTP client abstraction (FgaResponse, HttpClient), error handling updates, SDK imports/exports, and comprehensive test coverage for the new fetch layer and error handling.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/drop-axios

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@dosubot
Copy link
Copy Markdown

dosubot Bot commented Mar 31, 2026

Related Documentation

1 document(s) may need updating based on files changed in this PR:

OpenFGA's Space

StreamedListObjects Feature Overview
View Suggested Changes
@@ -115,7 +115,7 @@
 ### JS SDK
 The JS SDK supports StreamedListObjects in v0.9.3 and later. The method streams results using async iterators, allowing you to process each object as it arrives. See the [documentation](#streamed-list-objects) for usage details.
 
-**Runtime Requirements:** The JS SDK requires Node.js v20.19.0 or higher. Tested and supported versions include Node.js 20.x, 22.x, 24.x, and 25.x. For detailed information about supported Node.js versions and the support policy, see [SUPPORTED_RUNTIMES.md](https://github.com/openfga/js-sdk/blob/main/SUPPORTED_RUNTIMES.md).
+**Runtime Requirements:** The JS SDK requires a runtime with native `fetch` and `ReadableStream` support. Supported runtimes include Node.js 20+, modern browsers, Deno, Cloudflare Workers, and Vercel Edge. The primary CI-tested Node.js versions are 20.x, 22.x, 24.x, and 25.x. For detailed information about supported runtimes and the support policy, see [SUPPORTED_RUNTIMES.md](https://github.com/openfga/js-sdk/blob/main/SUPPORTED_RUNTIMES.md).
 
 Additionally, the JS SDK provides `executeApiRequest` and `executeStreamedApiRequest` methods on `OpenFgaClient` that can be used to call arbitrary API endpoints that don't yet have dedicated SDK methods. Use `executeApiRequest` for regular endpoints and `executeStreamedApiRequest` for streaming endpoints like `/streamed-list-objects`. These methods act as an escape hatch while still providing SDK benefits like authentication, configuration, retries, and error handling.
 

[Accept] [Decline]

Note: You must be authenticated to accept/decline updates.

How did I do? Any feedback?  Join Discord

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR migrates the SDK’s transport layer from Axios to the native fetch API to improve cross-runtime support (Node.js, browsers, Deno/edge-like environments) and reduce dependencies, while updating the public response surface ($response) and HTTP injection mechanism.

Changes:

  • Replaces Axios usage with a fetch-based HttpClient abstraction and introduces FgaResponse<T> as the new $response type.
  • Refactors retry/timeout/error handling to work with fetch + AbortSignal.timeout(), including streamed responses via ReadableStream.
  • Updates docs and tests to reflect the new HTTP stack and breaking API changes.

Reviewed changes

Copilot reviewed 15 out of 15 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
common.ts Core migration: implements fetch-based request execution, retries, response parsing, and new HttpClient/FgaResponse types.
api.ts Updates generated API surface to accept HttpClient injection and uses fetch-based request functions.
base.ts Replaces Axios setup with HttpClient initialization and passes it into credentials/API layers.
client.ts Switches client $response types to FgaResponse and adjusts streaming behavior to ReadableStream.
errors.ts Decouples errors from Axios by introducing HttpErrorContext and updating error constructors.
credentials/credentials.ts Migrates token exchange to fetch-based requests via HttpClient.
configuration.ts Tightens typing for baseOptions and updates comments to reflect HTTP (not Axios) semantics.
index.ts Re-exports new HTTP/response types from common.ts.
package.json Removes axios dependency.
tests/fetch-http-client.test.ts Adds focused tests for fetch client behavior (headers, retries, timeout, parsing, injection).
tests/index.test.ts Updates retry/non-network error test to mock fetch instead of Axios.
tests/errors-authentication.test.ts Updates authentication error test to use HttpErrorContext.
README.md Documents custom HTTP client usage and the new $response type.
SUPPORTED_RUNTIMES.md Broadens runtime support documentation beyond Node.js.
CHANGELOG.md Records breaking changes and migration notes for the Axios → fetch switch.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread common.ts Outdated
Comment thread common.ts Outdated
Comment thread errors.ts
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread client.ts Outdated
@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Mar 31, 2026

Codecov Report

❌ Patch coverage is 76.20690% with 69 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.40%. Comparing base (d09bb3d) to head (e2e8878).

Files with missing lines Patch % Lines
api.ts 59.22% 42 Missing ⚠️
common.ts 83.33% 16 Missing and 5 partials ⚠️
errors.ts 87.80% 0 Missing and 5 partials ⚠️
client.ts 85.71% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #366      +/-   ##
==========================================
+ Coverage   85.80%   86.40%   +0.60%     
==========================================
  Files          26       26              
  Lines        1268     1339      +71     
  Branches      225      277      +52     
==========================================
+ Hits         1088     1157      +69     
- Misses        110      115       +5     
+ Partials       70       67       -3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@socket-security
Copy link
Copy Markdown

socket-security Bot commented Mar 31, 2026

All alerts resolved. Learn more about Socket for GitHub.

This PR previously contained dependency changes with security issues that have been resolved, removed, or ignored.

View full report

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@api.ts`:
- Around line 487-490: The FP streaming function signature is incorrect: update
executeStreamedApiRequest's returned inner function type from PromiseResult<any>
to Promise<any> to reflect that createStreamingRequestFunction (the
implementation) returns the raw response.data stream; likewise change the
OO/public streaming method's return type from Promise<CallResult<any>> to
Promise<any> so callers don't expect a $response property—locate the
executeStreamedApiRequest implementation that calls RequestBuilder and
createStreamingRequestFunction (and the public method that wraps it where
TelemetryAttribute.FgaClientRequestMethod is set) and adjust their declared
return types to Promise<any>.

In `@common.ts`:
- Around line 376-382: Replace the naive JSON check with the existing MIME
helper: instead of using contentType.includes("application/json") check the full
JSON MIME family by calling isJsonMime(contentType) (use the local contentType
variable and keep the existing branches that set data via response.json() or
response.text()); update the conditional in the block that inspects
response.headers/get("content-type") so responses like application/problem+json,
vendor+json, or mixed-case types are parsed with response.json().
- Around line 721-724: The call to attemptHttpRequest currently omits the
telemetry block so streaming requests (and retries) don't emit
histogramHttpRequestDuration; update the call where wrappedResponse is assigned
to pass the telemetry through (e.g., include telemetry in the options object
alongside maxRetry and minWaitInMs or as the appropriate parameter expected by
attemptHttpRequest) so attemptHttpRequest receives the telemetry and can emit
histogramHttpRequestDuration; reference the existing symbols attemptHttpRequest,
wrappedResponse, fetchRequestConfig, maxRetry, minWaitInMs, and client to locate
and modify the call.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8225e0a3-58cf-42ce-9769-864c555633b7

📥 Commits

Reviewing files that changed from the base of the PR and between d09bb3d and debd645.

📒 Files selected for processing (15)
  • CHANGELOG.md
  • README.md
  • SUPPORTED_RUNTIMES.md
  • api.ts
  • base.ts
  • client.ts
  • common.ts
  • configuration.ts
  • credentials/credentials.ts
  • errors.ts
  • index.ts
  • package.json
  • tests/errors-authentication.test.ts
  • tests/fetch-http-client.test.ts
  • tests/index.test.ts
💤 Files with no reviewable changes (1)
  • package.json

Comment thread api.ts Outdated
Comment thread common.ts
Comment thread common.ts Outdated
@rhamzeh rhamzeh requested a review from Copilot March 31, 2026 23:40
@rhamzeh
Copy link
Copy Markdown
Member Author

rhamzeh commented Mar 31, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 31, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 16 out of 17 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread common.ts
Comment thread common.ts Outdated
Comment thread common.ts Outdated
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 1, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@common.ts`:
- Around line 750-773: createStreamingRequestFunction currently returns the raw
stream (response?.data) while client code (executeStreamedApiRequest and the
handler that checks stream.$response.data) expects a PromiseResult/$response
envelope; standardize by changing createStreamingRequestFunction to return the
same envelope shape as other requests (e.g., an object with $response containing
the full response and data as the stream), and
update/createStreamingRequestFunction's public signature/docs to
PromiseResult<any>, plus adjust any consumer code in client.ts that expects
stream.$response.data to use the unified envelope returned by
createStreamingRequestFunction (references: createStreamingRequestFunction,
executeStreamedApiRequest, and the handler that inspects stream.$response.data).
- Around line 306-320: The current logic always attaches an AbortSignal
(timeoutMs) which will abort even after fetch resolves and thus cuts off
streaming responses; modify the timeout creation to skip adding the implicit
default timeout when the request is a streamed response (check
request.responseType === "stream" or similar) unless the caller explicitly
provided request.timeout; i.e., only set signal when request.timeout is defined
or request.responseType !== "stream" (keep using timeoutMs, AbortSignal.timeout
fallback and AbortController fallback as before), and add a regression test that
issues a streaming request that remains open past the default 10s to ensure
streams are not aborted by HttpClient.defaultTimeout.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 4bc75d66-f8e3-4796-90b1-84a6bc52480c

📥 Commits

Reviewing files that changed from the base of the PR and between debd645 and ef3a638.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (9)
  • CHANGELOG.md
  • README.md
  • SUPPORTED_RUNTIMES.md
  • api.ts
  • client.ts
  • common.ts
  • errors.ts
  • tests/errors.test.ts
  • tests/fetch-http-client.test.ts
✅ Files skipped from review due to trivial changes (4)
  • SUPPORTED_RUNTIMES.md
  • CHANGELOG.md
  • README.md
  • api.ts

Comment thread common.ts Outdated
Comment thread common.ts
SoulPancake
SoulPancake previously approved these changes Apr 1, 2026
@SoulPancake SoulPancake self-requested a review April 2, 2026 07:15
…T" to avoid undefined request method, streaming timeout kills long-running streams
@emilic emilic force-pushed the feat/drop-axios branch from ab3ee8f to 79ee8af Compare April 6, 2026 08:46
emilic added 2 commits April 6, 2026 04:59
…cts. With fetch, createStreamingRequestFunction returns the raw ReadableStream directly — $response is never set, so the fallback was the only path that ever executed.
…rror properties

Overwriting .constructor and .name on an error doesn't actually change its type, so instanceof checks failed for non-401/403 token endpoint errors.
(err as any).clientId = clientCredentials.clientId;
(err as any).audience = clientCredentials.apiAudience;
(err as any).grantType = "client_credentials";
const authErr = new FgaApiAuthenticationError({
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

My only issue with this is that we're losing the stack trace for the rethrown error.

I believe this is the better approach: https://github.com/openfga/js-sdk/pull/329/changes

@derekcicerone
Copy link
Copy Markdown

derekcicerone commented May 5, 2026

It would be awesome to get this one in/released since axios 1.15 currently has 10+ security alerts (and had even bigger issues earlier this year). Btw huge fans of OpenFGA - thanks for building it!

@rhamzeh
Copy link
Copy Markdown
Member Author

rhamzeh commented May 5, 2026

It would be awesome to get this one in/released since axios 1.15 currently has 10+ security alerts (and had even bigger issues earlier this year). Btw huge fans of OpenFGA - thanks for building it!

Thanks for your kind words @derekcicerone! We're 100% aiming to getting it merged soon, just some minor tweaks to be made first.

That said, don't expect it out this week, we intend for it to go out with another branch we're working on - ESM Modules. As both are pretty big changes, we'll release both in the same release.

Hopefully it will be out soon though! 🤞🏽

@rhamzeh
Copy link
Copy Markdown
Member Author

rhamzeh commented May 5, 2026

In the meantime, we'll bump axios to v1.16.0 and push out a release - thanks for the heads up.

https://github.com/axios/axios/releases/tag/v1.15.1 and https://github.com/axios/axios/releases/tag/v1.15.2 both have security notices neither dependabot, nor snyk or socket.dev flagged for us - thank you for the heads up!

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.

Replace axios

6 participants