Package Exports
- fexios
- fexios/dist/fexios.d.ts
- fexios/dist/index.d.ts
- fexios/dist/index.js
- fexios/dist/index.js.map
- fexios/dist/models/callable-instance.d.ts
- fexios/dist/models/errors.d.ts
- fexios/dist/models/header-builder.d.ts
- fexios/dist/models/query-builder.d.ts
- fexios/dist/models/response.d.ts
- fexios/dist/plugins/cookie-jar/CookieJar.d.ts
- fexios/dist/plugins/cookie-jar/index.d.ts
- fexios/dist/plugins/index.d.ts
- fexios/dist/plugins/index.js
- fexios/dist/plugins/index.js.map
- fexios/dist/types.d.ts
- fexios/dist/utils.d.ts
- fexios/package.json
- fexios/plugins
Readme
Fexios
Fetch based HTTP client with similar API to axios for browser and Node.js
fetch + axios = fexios (Just a joke)
Features
- 🤯 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:
index.umd.cjs 8.51 kB │ gzip: 3.48 kB │ map: 31.96 kB
Installation
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:
import fexios, { createFexios, Fexios } from 'fexios'
// Using directly
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' },
})
// With options
const fexios = createFexios(/* options */)
const fexios = new Fexios(/* options */)
const fexios = Fexios.create(/* options */)Use directly in the browser
- JS Module
import('https://unpkg.com/fexios?module').then(({ createFexios }) => {
const fexios = createFexios(/* options */)
})- Global variables
<script src="https://unpkg.com/fexios"></script>
<script>
// Using directly
fexios.get('https://zh.moegirl.org.cn/api.php')
// With options
const { createFexios } = Fexios
const fexios = createFexios(/* options */)
</script>Compatibility
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.
Usage
You can find some sample code snippets here.
new Fexios(configs: Partial<FexiosConfigs>)
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' | 'blob' | 'text' | 'stream' | 'arrayBuffer'
fetch?: FetchLike
}Defaults
const DEFAULT_CONFIGS = {
baseURL: '',
credentials: 'same-origin',
headers: {
'content-type': 'application/json; charset=UTF-8',
},
query: {},
responseType: 'json',
fetch: globalThis.fetch,
}Fexios#request(config: FexiosRequestOptions)
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> = Omit<
FexiosContext<T>,
'rawResponse' | 'response' | 'data' | 'headers'
> & {
rawResponse: Response
response: IFexiosResponse<T>
headers: Headers
data: T
}
export interface IFexiosResponse<T = any> {
ok: boolean
status: number
statusText: string
headers: Headers
rawResponse: Response
data: T
}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]])
Automatic Merge for Queries/Headers
The url/query/headers parameters you pass in various places, as well as parameters modified in hook callbacks, will be automatically merged in order and according to the following rules to build the complete request URL and request headers.
Context Overwriting Rules
ctx.querycould be:Record<string, any> | URLSearchParamsctx.headerscould be:Record<string, string | string[] | null | undefined> | Headers
Basic merging rules:
undefinedvalue means no change for that key (keep from lower layer)nullvalue means remove that key (delete, regardless of lower layer)- Other values: overwrite with the new value
Details:
- Queries
- Accepts
Record<string, any>orURLSearchParams(internally supports conversion fromstring/FormData/Mapetc. to objects for merging). - Arrays are expanded as duplicate keys; if the key name ends with
[](e.g.,'tags[]'), it is forced to output with the[]suffix. - Nested objects are expanded as
a[b][c]=...;undefinedpreserves the lower layer value,nullcompletely removes the key.
- Accepts
- Headers
- Case-insensitive, internally processed according to
Headerssemantics. string[]first deletes the original value and then appends each item;undefinedpreserves,nulldeletes, normal values use set to overwrite.- Automatic content type: When
content-typeis not explicitly specified, JSON object bodies are serialized and set toapplication/json;FormData/URLSearchParamslet the runtime set it automatically (equivalent to setting the key tonull).
- Case-insensitive, internally processed according to
See header-builder.spec.ts and query-builder.spec.ts for more examples.
Merge Priority
For easier understanding, the following describes "layers" from high to low; high layers can override low layers, undefined means "keep the lower layer value", null means "remove from the final result".
Without hooks (first normalization)
- Query:
ctx.query(request options) >ctx.url(request URL's search part) >baseConfigs.query>baseURL's search - Headers:
request options.headers>baseConfigs.headers
- Query:
With hooks (normalization after hooks)
- Query:
ctx.query(modified by hooks) >ctx.url(modified URL's search by hooks) > original request URL's search (before hooks) >baseConfigs.query>baseURL's search - Headers:
ctx.headers(modified by hooks) >request options.headers>baseConfigs.headers
- Query:
Additional rules (consistent with unit tests):
- If a key is set to
undefinedin hooks, the same key will not be overwritten by the "request URL layer" and will retain the lower layer value (usually the base layer). - If a key is set to
null, it will be removed from the final result regardless of whether it exists in lower layers.
Example:
base: keep=baseKeep
request URL: keep=reqKeep
hook: ctx.query.keep = undefined
=> result keep=baseKeep (request URL ignored, base retained)
base: rm=baseRemove
hook: ctx.query.rm = null
=> result rm removedHooks
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>
export interface FexiosContext<T = any> extends FexiosRequestOptions {
url: string // may changes after beforeInit
rawRequest?: Request // provide in beforeRequest
rawResponse?: Response // provide in afterRequest
response?: IFexiosResponse // provide in afterRequest
data?: T // provide in afterRequest
}Hooks example
const fexios = new Fexios()
fexios.on('beforeRequest', async (ctx) => {
ctx.headers.authorization = localStorage.getItem('token')
if (ctx.query.foo === 'bar') {
return false
} else {
ctx.query.foo = 'baz'
return ctx
}
return ctx
})beforeInit
All context passed as is. You can do custom conversions here.
beforeRequest
Pre-converted done.
afterBodyTransformed
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.
beforeActualFetch
ctx.rawRequest:{Request}now available.
The Request instance has been generated.
At this time, you cannot modify the ctx.url, ctx.query, ctx.headers or ctx.body (etc.) anymore. Unless you pass a brand new Request to replace ctx.rawRequest.
afterResponse
Anything will be read-only at this time.
ctx is FexiosFinalContext now.
Short-circuit Response
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' },
})
})interceptors
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) => {})Plugin
import type { FexiosPlugin } from 'fexios'
const authPlugin: FexiosPlugin = (app) => {
app.on('beforeRequest', (ctx) => {
ctx.headers = { ...ctx.headers, Authorization: 'Bearer token' }
return ctx
})
return app // You can return app, or omit the return value
}
const fx = new Fexios().plugin(authPlugin)License
MIT License
Copyright (c) 2023 机智的小鱼君 (A.K.A. Dragon-Fish)