<a href="https://margelo.com">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/margelo/react-native-nitro-fetch/main/assets/banner-dark.png" />
<source media="(prefers-color-scheme: light)" srcset="https://raw.githubusercontent.com/margelo/react-native-nitro-fetch/main/assets/banner-light.png" />
<img alt="Nitro Modules" src="https://raw.githubusercontent.com/margelo/react-native-nitro-fetch/main/assets/banner-light.png" />
</picture>
</a>
<br />
**react-native-nitro-fetch** is a general purpose network fetching library for React Native. It can be used as a drop-in replacement for the built-in `fetch(...)` method, as well as provide additional features like prefetching and workletized mappers.
<p align="center">
<a href="https://fetch.margelo.com"><b>Documentation</b></a>
</p>
## Features
- 🔧 Drop-in replacement for the built-in `fetch(...)` method
- ⚡️ Fast HTTP stack using [Cronet](https://chromium.googlesource.com/chromium/src/+/lkgr/components/cronet/README.md) on Android, and [URLSession](https://developer.apple.com/documentation/Foundation/URLSession) on iOS
- 💪 Supports HTTP/1, HTTP/2 and [HTTP/3](https://en.wikipedia.org/wiki/HTTP/3) over [QUIC](https://www.chromium.org/quic/), [Brotli](https://github.com/google/brotli), and disk cache
- ⏰ Prefetching on app-startup for even faster initialization
- 🧵 Worklet support for parallel data mapping without blocking the JS Thread
- 🔌 Optional **WebSockets** via [`react-native-nitro-websockets`](docs/websockets.md) (see [WebSockets & prewarm](#websockets--prewarm) below)
- 🔥 Powered by [Nitro Modules](https://github.com/mrousavy/nitro)
## Installation
```sh
npm i react-native-nitro-fetch react-native-nitro-modules
```
> [Nitro Modules](https://github.com/mrousavy/nitro) requires react-native 0.75+ or higher
**WebSockets (optional)** — add the companion socket package plus **text decoder** (peer dependency of websockets):
```sh
npm i react-native-nitro-websockets react-native-nitro-text-decoder
```
Full setup, native hooks, prewarm, and API details: **[docs/websockets.md](docs/websockets.md)** · UI: [`example/src/screens/WebSocketScreen.tsx`](example/src/screens/WebSocketScreen.tsx) · auth + prewarm: [Token refresh](#token-refresh-cold-start) (example block).
## Usage
To simply fetch data, import the `fetch(...)` method from `react-native-nitro-fetch`:
```ts
import { fetch } from 'react-native-nitro-fetch'
const res = await fetch('https://httpbin.org/get')
const json = await res.json()
```
This can be used as a drop-in-replacement for the built-in `fetch(...)` method.
### Prefetching in JS
You can prefetch a URL in JS, which keeps the result cached for the next actual `fetch(...)` call - this can be used shortly before navigating to a new screen to have results hot & ready:
```ts
import { prefetch } from 'react-native-nitro-fetch'
await prefetch('https://httpbin.org/uuid', {
headers: { prefetchKey: 'uuid' }
})
```
Then, on the new screen that was navigated to:
```ts
import { fetch } from 'react-native-nitro-fetch'
const res = await fetch('https://httpbin.org/uuid', {
headers: { prefetchKey: 'uuid' }
})
console.log('prefetched header:', res.headers.get('nitroPrefetched'))
```
### Prefetching for the next app launch
Prefetching data on app launch (or _process start_) will make it hot & ready once your JS code actually runs. Call `prefetchOnAppStart(...)` to enqueue a prefetch for the **next** app start:
```ts
import { prefetchOnAppStart } from 'react-native-nitro-fetch'
await prefetchOnAppStart('https://httpbin.org/uuid', {
prefetchKey: 'uuid'
})
```
Then, once the app opens the next time, a call to `fetch(...)` might resolve faster since it will contain already cached results:
```ts
import { fetch } from 'react-native-nitro-fetch'
const res = await fetch('https://httpbin.org/uuid', {
headers: { prefetchKey: 'uuid' }
})
console.log('prefetched header:', res.headers.get('nitroPrefetched'))
```
In our tests, prefetching alone yielded a **~220 ms** faster TTI (time-to-interactive) time! 🤯
### Token refresh (cold start)
When you use **auto-prefetch** (`prefetchOnAppStart`) and/or **WebSocket prewarm on app start** (`react-native-nitro-websockets`), native code runs **before** your JS bundle. If those requests need auth headers, you can register a **token refresh** configuration. On each cold start, native code calls your refresh URL, maps the response into HTTP headers, and merges them into auto-prefetches and/or WebSocket prewarms.
**1. Register the refresh config** (persisted in encrypted native storage):
```ts
import { registerTokenRefresh } from 'react-native-nitro-fetch'
registerTokenRefresh({
target: 'fetch', // 'websocket' | 'fetch' | 'all'
url: 'https://api.example.com/oauth/token',
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ grant_type: 'client_credentials' }),
responseType: 'json',
mappings: [
{ jsonPath: 'access_token', header: 'Authorization', valueTemplate: 'Bearer {{value}}' },
],
// If the refresh request fails:
// - 'useStoredHeaders' — use last successful headers from the previous run (default)
// - 'skip' — skip auto-prefetch / prewarm entirely when refresh fails
onFailure: 'useStoredHeaders',
})
```
**Response mapping**
- Default `responseType` is `'json'`. Use **`mappings`** to copy fields from the JSON body into header names (dot paths supported, e.g. `data.token`).
- Use **`compositeHeaders`** to build a header from a template and multiple JSON paths (`{{placeholder}}` in the template).
- For a plain-text body, set `responseType: 'text'` and use **`textHeader`** / optional **`textTemplate`** (with `{{value}}`).
- To inject into the prefetch's **JSON body** or **form-data** (the `fetch` path only), use **`bodyMappings`** / **`formDataMappings`** (see below).
**Example: header + JSON body + form-data mappings**
One config can fan the same refreshed value into all three destinations. Each mapping only applies to a prefetch whose body it matches — `bodyMappings` won't synthesize a JSON body on a GET/form request, and `formDataMappings` won't make a request multipart.
```ts
import { registerTokenRefresh } from 'react-native-nitro-fetch'
// Refresh endpoint returns e.g. { "data": { "accessToken": "abc123" } }
registerTokenRefresh({
target: 'fetch',
url: 'https://api.example.com/oauth/token',
method: 'POST',
responseType: 'json',
// 1) header mapping → sets a request header
mappings: [
{ jsonPath: 'data.accessToken', header: 'Authorization', valueTemplate: 'Bearer {{value}}' },
],
// 2) JSON-body mapping → sets a (nested) dot-path key in the prefetch's JSON body
bodyMappings: [
{ jsonPath: 'data.accessToken', bodyPath: 'auth.token' },
],
// 3) form-data mapping → replaces/appends a multipart field by name
formDataMappings: [
{ jsonPath: 'data.accessToken', field: 'token' },
],
onFailure: 'useStoredHeaders',
})
```
Result, per prefetch:
- a JSON prefetch of `{ "deviceId": "d-1" }` → `Authorization: Bearer abc123` **and** body `{ "deviceId": "d-1", "auth": { "token": "abc123" } }`
- a form-data prefetch → `Authorization: Bearer abc123` **and** a `token=abc123` part
> For `responseType: 'text'`, the body/form equivalents of `textHeader` are **`bodyTextPath`** / **`formDataTextField`**.
**Example: token refresh + WebSocket prewarm**
```ts
import { registerTokenRefresh } from 'react-native-nitro-fetch'
import { prewarmOnAppStart, NitroWebSocket } from 'react-native-nitro-websockets'
const WSS = 'wss://api.example.com/live'
registerTokenRefresh({
target: 'websocket', // use 'all' if you also use prefetchOnAppStart with the same token flow
url: 'https://api.example.com/oauth/token',
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
grant_type: 'client_credentials',
client_id: '…',
client_secret: '…',
}),
mappings: [
{ jsonPath: 'access_token', header: 'Authorization', valueTemplate: 'Bearer {{value}}' },
],
})
```
**3. Optional JS helpers**
```ts
import {
callRefreshEndpoint,
clearTokenRefresh,
getStoredTokenRefreshConfig,
} from 'react-native-nitro-fetch'
// Same mapping rules as native; uses global fetch from JS
const headers = await callRefreshEndpoint(config)
// Remove stored config and token caches (scope with 'fetch' | 'websocket' | 'all')
clearTokenRefresh('fetch')
// Read back what was registered (or null)
const stored = getStoredTokenRefreshConfig('fetch')
```
The refresh config and header caches are stored with **platform secure storage** (Android Keystore + encrypted values in `SharedPreferences`, iOS Keychain-backed encryption in the same `UserDefaults` suite as other nitro keys).
### AbortController
Cancel in-flight requests using the standard `AbortController` API:
```ts
import { fetch } from 'react-native-nitro-fetch'
const controller = new AbortController()
// Abort after 500ms
setTimeout(() => controller.abort(), 500)
try {
const res = await fetch('https://httpbin.org/delay/20', {
signal: controller.signal,
})
} catch (e) {
if (e.name === 'AbortError') {
console.log('Request was cancelled')
}
}
```
Pre-aborted signals are also supported — the request will throw immediately without making a network call:
```ts
const controller = new AbortController()
controller.abort()
await fetch(url, { signal: controller.signal }) // throws AbortError
```
### FormData
Upload files and form fields using `FormData`:
```ts
import { fetch } from 'react-native-nitro-fetch'
const fd = new FormData()
fd.append('username', 'nitro_user')
fd.append('avatar', {
uri: 'file:///path/to/photo.jpg',
type: 'image/jpeg',
name: 'avatar.jpg',
} as any)
const res = await fetch('https://httpbin.org/post', {
method: 'POST',
body: fd,
})
const json = await res.json()
```
### Worklet Mapping
Since Nitro Fetch is a [Nitro Module](https://nitro.margelo.com), it can be used from Worklets.
This can be useful to parse data without blocking the main JS-Thread:
```ts
import { nitroFetchOnWorklet } from 'react-native-nitro-fetch'
const data = await nitroFetchOnWorklet(
'https://httpbin.org/get',
undefined,
(payload) => {
'worklet'
return JSON.parse(payload.bodyString ?? '{}')
}
)
```
Before using worklet mapping, install and configure [react-native-worklets](https://docs.swmansion.com/react-native-worklets/docs/).
### Streaming with `TextDecoder`
Nitro Fetch can also expose an streaming mode that returns a `ReadableStream` body.
Combined with [`react-native-nitro-text-decoder`](https://github.com/margelo/react-native-nitro-fetch/tree/main/packages/react-native-nitro-text-decoder), you can incrementally decode UTF‑8 chunks:
```tsx
import { useRef, useState } from 'react'
import { fetch as nitroFetch } from 'react-native-nitro-fetch'
import { TextDecoder } from 'react-native-nitro-text-decoder'
export function StreamingExample() {
const [output, setOutput] = useState('')
const decoder = useRef(new TextDecoder())
const append = (text: string) => {
setOutput(prev => prev + text)
}
const runStream = async () => {
// `stream: true` enables the streaming transport
const res = await nitroFetch('https://httpbin.org/stream/20', {
stream: true,
})
const reader = res.body?.getReader()
if (!reader) {
append('No readable stream!')
return
}
let chunks = 0
while (true) {
const { done, value } = await reader.read()
if (done) break
chunks++
const text = decoder.current.decode(value, { stream: true })
append(text)
}
append(`\n\n✅ Done — ${chunks} chunk(s) received`)
}
// Call `runStream()` from a button handler in your UI
}
```
### WebSockets & prewarm
Use **[react-native-nitro-websockets](docs/websockets.md)** for `NitroWebSocket` (browser-like API: `onopen`, `onmessage`, `send`, `close`, …). Install **`react-native-nitro-text-decoder`** alongside it — the socket package uses it to decode UTF-8 text frames.
**Prewarm on next launch** — queue URLs from JS so native code can start the handshake before React loads:
```ts
import {
prewarmOnAppStart,
removeFromPrewarmQueue,
clearPrewarmQueue,
} from 'react-native-nitro-websockets'
prewarmOnAppStart('wss://echo.websocket.org')
// optional: prewarmOnAppStart(url, ['subproto'], { Authorization: 'Bearer …' })
clearPrewarmQueue()
removeFromPrewarmQueue('wss://echo.websocket.org')
```
On **Android**, call `NitroWebSocketAutoPrewarmer.prewarmOnStart(this)` in `Application.onCreate` (see [example `MainApplication.kt`](example/android/app/src/main/java/nitrofetch/example/MainApplication.kt)). **iOS** picks up the queue via the linked pod.
Authenticated prewarms: use **`registerTokenRefresh`** with `target: 'websocket'` or `'all'` — see [Token refresh (cold start)](#token-refresh-cold-start) for a **small `registerTokenRefresh` + `prewarmOnAppStart` + `NitroWebSocket` example**.
More detail: **[docs/websockets.md](docs/websockets.md)** · UI sample: **[example/src/screens/WebSocketScreen.tsx](example/src/screens/WebSocketScreen.tsx)**.
## Benchmarks
These numbers are **without prefetch** — a common misconception is that nitro-fetch is only faster when prefetching. It isn't: plain `fetch` is faster on its own. In our testing it's around **15–25% faster** than the built-in `fetch` (release build, idle, Wi-Fi). The gap depends a lot on your backend — connection reuse, HTTP/2, and **HTTP/3 over QUIC** widen it. With [prefetch](#prefetching-in-js) the difference is far larger still.
| | Built-in | Nitro | Faster |
| --- | ---: | ---: | ---: |
| Average (10 runs) | 192 ms | 147 ms | **23.2%** (1.30×) |
| Median (10 runs) | 190 ms | 148 ms | **22.0%** (1.28×) |
Try it yourself in the example app. See [Benchmarks](https://fetch.margelo.com/docs/benchmarks) for the method and full table. Concerns about the benchmark? [Open an issue](https://github.com/margelo/react-native-nitro-fetch/issues).
## Limitations & Alternatives
- **WebSockets** are not part of `react-native-nitro-fetch` itself; use the companion package **[react-native-nitro-websockets](docs/websockets.md)** (with **react-native-nitro-text-decoder**). For other stacks, [react-native-fast-io](https://github.com/callstackincubator/react-native-fast-io) is another option.
## Documentation
You can find documentation at https://fetch.margelo.com
## Margelo
Nitro Fetch is built with ❤️ by Margelo.
We build fast and beautiful apps. Contact us at [margelo.com](https://margelo.com) for high-end consultancy services.
## Contributing
- Development workflow: `CONTRIBUTING.md#development-workflow`
- Sending a pull request: `CONTRIBUTING.md#sending-a-pull-request`
- Code of conduct: `CODE_OF_CONDUCT.md`
## Authors
- [Szymon Kapala](https://github.com/Szymon20000)
- [Alex Shumihin](https://github.com/pioner92)
- [Ronald Goedeke](https://github.com/ronickg)
- [Marc Rousavy](https://github.com/mrousavy)
- [Ritesh Shukla](https://github.com/riteshshukla04)
## License
MIT