Fetch based HTTP client with similar API to axios for browser and Node.js
fetch + axios = fexios (Just a joke)
- 🤯 Native fetch API (supports the Promise API)
- 🤫 Method shortcuts (
fexios.post()) - 🔗 Hooks (intercept request and response)
- 😏 Automatic transform request and response data
- 😏 Automatic transforms for JSON data
- 🤩 Instances with custom defaults
- 🫡 Instance extendable
- 😍 Fricking tiny size: ~6kb (gzipped)
Using package manager
# Node Package Manager
npm install fexios
# Why not pnpm
pnpm add fexios
# Or yarn?
yarn add fexiosThen import the library and enjoy:
// Using directly
import fexios from 'fexios'
fexios.get('https://zh.moegirl.org.cn/api.php')
// Yes, it's callable! Just like axios
fexios({
url: 'https://zh.moegirl.org.cn/api.php',
method: 'GET',
})
fexios('https://zh.moegirl.org.cn/api.php', {
method: 'POST',
body: { foo: 'bar' },
})
// Customize instance
import { createFexios, Fexios } from 'fexios'
const fexios = createFexios(/* options */)
const fexios = new Fexios(/* options */)
const fexios = Fexios.create(/* options */)
// Custom instance is also callable
fexios('https://zh.moegirl.org.cn/api.php', {
method: 'POST',
body: { foo: 'bar' },
})Use directly in the browser
- ES Module
import('https://unpkg.com/fexios?module').then(({ createFexios }) => {
const fexios = createFexios(/* options */)
})- UMD bundle
<script src="https://unpkg.com/fexios/lib/index.js"></script>
<script>
// Using directly
const { fexios } = window.Fexios
fexios.get('https://zh.moegirl.org.cn/api.php')
// With options
const { createFexios } = window.Fexios
const fexios = createFexios(/* options */)
</script>Refer: https://developer.mozilla.org/docs/Web/API/Fetch_API
| Chrome | Edge | Firefox | Opera | Safari | Node.js |
|---|---|---|---|---|---|
| 42 | 14 | 39 | 29 | 10.1 (iOS 10.3) | ^16.15.0 || >=18.0.0 |
* Abort signal requires higher version.
You can find some sample code snippets here.
FexiosConfigs
export interface FexiosConfigs {
baseURL: string
timeout: number
/**
* In context, query value can be:
* - `null` - to remove the item
* - `undefined` - to keep the item as is
*/
query: Record<string, any> | URLSearchParams
headers: Record<string, string | string[]> | Headers
credentials?: RequestInit['credentials']
cache?: RequestInit['cache']
mode?: RequestInit['mode']
responseType?: 'json' | 'text' | 'form' | 'blob' | 'arrayBuffer'
fetch?: FetchLike
}Defaults
const DEFAULT_CONFIGS = {
baseURL: '',
timeout: 0,
credentials: undefined,
headers: {},
query: {},
responseType: undefined,
shouldThrow(response) {
return !response.ok
},
fetch: globalThis.fetch,
}fexios.request<T>(config): Promise<FexiosResponse<T>>
FexiosRequestOptions
export interface FexiosRequestOptions extends Omit<FexiosConfigs, 'headers'> {
url?: string | URL
method?: FexiosMethods
/**
* In context, header value can be:
* - `null` - to remove the header
* - `undefined` - to keep the header as is
*/
headers: Record<string, string | string[] | null | undefined> | Headers
body?: Record<string, any> | string | FormData | URLSearchParams
abortController?: AbortController
onProgress?: (progress: number, buffer?: Uint8Array) => void
}returns {FexiosFinalContext}
export type FexiosFinalContext<T = any> = {
request: {
url: string
method?: string
headers: Headers | Record<string, any>
query: Record<string, any> | URLSearchParams
body?: any
rawRequest: Request
// ... other request configs ...
}
runtime: {
abortController?: AbortController
onProgress?: (progress: number, buffer?: Uint8Array) => void
customEnv?: any
}
/** Parsed response wrapper */
response: IFexiosResponse<T>
/** Unread original Response */
rawResponse: Response
/** Shortcut getters */
readonly headers: Headers
readonly data: T
readonly responseType: 'json' | 'text' | 'form' | 'blob' | 'arrayBuffer'
readonly url: string
readonly rawRequest: Request
}
export interface IFexiosResponse<T = any> {
ok: boolean
status: number
statusText: string
headers: Headers
rawResponse: Response
data: T
responseType: 'json' | 'text' | 'form' | 'blob' | 'arrayBuffer'
}And common request methods aliases:
- fexios.get(url[, config])
- fexios.delete(url[, config])
- fexios.head(url[, config])
- fexios.options(url[, config])
- fexios.post(url[, data[, config]])
- fexios.put(url[, data[, config]])
- fexios.patch(url[, data[, config]])
The url/query/headers parameters you pass in various places will be automatically merged to build the complete request.
Fexios uses a simplified 2-stage merge strategy:
This happens only ONCE, immediately after the beforeInit hook.
- URL:
ctx.request.urlis resolved againstdefaults.baseURL.- Search params from
defaults.baseURLare merged intoctx.request.url. - Priority:
ctx.request.urlsearch params >defaults.baseURLsearch params.
- Search params from
- Query:
defaults.queryis merged intoctx.request.query.- Priority:
ctx.request.query>defaults.query.
- Priority:
- Headers:
defaults.headersis merged intoctx.request.headers.- Priority:
ctx.request.headers>defaults.headers.
- Priority:
This happens when constructing the native Request object.
- Query:
ctx.request.queryis merged into the final URL's search params.- Priority:
ctx.request.query> URL search params (from step 1 or modified by hooks).
- Priority:
- Headers: Final headers are built.
- undefined: Keeps the value from the lower layer (or no change).
- null: Removes the key from the result.
- value: Overwrites the lower layer.
- Modifications to
ctx.request.urlin hooks (e.g.beforeRequest) will NOT be parsed intoctx.request.query. They are treated as separate entities until the final merge. - If you replace
ctx.request.urlin a hook, you lose the original URL search params unless you manually preserve them. - To modify query parameters reliably in hooks, prefer operating on
ctx.request.query.
You can modify context in hooks' callback then return it as a brand new context™.
Return false to abort request immediately.
export type FexiosHook<C = unknown> = (
context: C
) => AwaitAble<C | void | false | Response>
export interface FexiosContext<T = any> {
request: {
url: string // may change after beforeInit
query: Record<string, any> | URLSearchParams
headers: Headers | Record<string, any>
body?: any
rawRequest?: Request // available in beforeActualFetch
}
runtime: {
abortController?: AbortController
onProgress?: (progress: number, buffer?: Uint8Array) => void
customEnv?: any
}
rawResponse?: Response // available in afterRawResponse
response?: IFexiosResponse<T> // available in afterResponse
// NOTE: legacy aliases like ctx.url / ctx.query may exist but are deprecated.
}Hooks example
const fexios = new Fexios()
fexios.on('beforeRequest', async (ctx) => {
;(ctx.request.headers as any).authorization = localStorage.getItem('token')
const q = ctx.request.query as any
if (q.foo === 'bar') {
return false
} else {
q.foo = 'baz'
return ctx
}
return ctx
})All context passed as is. You can do custom conversions here.
Pre-converted done.
ctx.body:{string|URLSearchParams|FormData|Blob}now available.
JSON body has been transformed to JSON string. Content-Type header has been set to body's type.
ctx.request.rawRequest:{Request}now available.
The Request instance has been generated.
At this time, you cannot modify ctx.request.url, ctx.request.query, ctx.request.headers or ctx.request.body (etc.) anymore, unless you pass a brand new Request to replace ctx.request.rawRequest.
Anything will be read-only at this time.
ctx is FexiosFinalContext now.
A hook callback can also return a Response at any time to short-circuit the request flow; Fexios will treat it as the final response and proceed to afterResponse:
fx.on('beforeActualFetch', () => {
return new Response(JSON.stringify({ ok: 1 }), {
status: 200,
headers: { 'content-type': 'application/json' },
})
})Oh, this is mimicked from axios. Just sweet sugar.
// They are the same
fexios.on('beforeRequest', async (ctx) => {})
fexios.interceptors.request.use((ctx) => {})
// Bro, they're just the same
fexios.on('afterResponse', async (ctx) => {})
fexios.interceptors.response.use((ctx) => {})See the plugin docs index: docs/plugins/README.md
Official plugins:
- Cookie Jar:
docs/plugins/cookie-jar.md - SSE (EventSource):
docs/plugins/sse.md - WebSocket:
docs/plugins/websocket.md - ...and maybe more?
MIT License
Copyright (c) 2023 机智的小鱼君 (A.K.A. Dragon-Fish)
Fexios' logo is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License (CC BY-SA 4.0).
Copyright (c) 2025 dragon-fish
This license applies ONLY to the logo files in
docs/logos/directory.