requete
is the French word forrequest
requete is a lightweight client-side HTTP request library based on the Fetch API, and supports middleware for processing requests and responses.
It provides APIs similar to Axios
.
In addition, requete also includes an XMLHttpRequest
adapter, which allows it to be used in older browsers that do not support Fetch
, and provides polyfills to simplify import.
Also, requete
supports usage in Node.js
, using fetch
API (nodejs >= 17.5.0
).
- Use
Fetch API
on modern browsers or Node.js - Use
XMLHttpRequest
on older browsers - Supports
middleware
for handling request and response - Supports the Promise API
- Transform request and response data
- Abort requests by
TimeoutAbortController
- Automatic transforms for JSON response data, and supports custom transformer
- Automatic data object serialization to
multipart/form-data
andx-www-form-urlencoded
body encodings
pnpm add requete
yarn add requete
npm i -S requete
<!-- using jsdelivr -->
<script src="https://cdn.jsdelivr.net/npm/requete/index.umd.min.js"></script>
<!-- or using unpkg -->
<script src="https://unpkg.com/requete/index.umd.min.js"></script>
First, you can import requete
and use it directly.
import requete from 'requete'
// Make a GET request
requete.get('https://httpbin.org/get')
// Make a POST request
requete.post('https://httpbin.org/post', { id: 1 })
You can also create an instance and specify request configs by calling the create()
function:
import { create } from 'requete'
const requete = create({ baseURL: 'https://httpbin.org' })
// Make a GET request
requete
.get<IData>('/post')
.then((r) => r.data)
.catch((error) => {
console.log(error) // error as `RequestError`
})
For Nodejs (commonjs):
const requete = require('requete')
// use default instance
requete.get('https://httpbin.org/post')
// create new instance
const http = requete.create({ baseURL: 'https://httpbin.org' })
// Make a POST request
http.post('/post', { id: 1 })
For browsers:
- UMD
<script src="https://cdn.jsdelivr.net/npm/requete"></script>
<script>
// use default instance
requete.get('https://httpbin.org/get')
// create new instance
const http = requete.create()
</script>
- ESM: by
index.browser.mjs
<script type="module">
import requete from 'https://cdn.jsdelivr.net/npm/requete/index.browser.mjs'
requete.get('https://httpbin.org/get')
</script>
- ESM: by
importmap
<script type="importmap">
{
"imports": {
"requete": "https://cdn.jsdelivr.net/npm/requete/index.mjs",
"requete/adapter": "https://cdn.jsdelivr.net/npm/requete/adapter.mjs",
"requete/shared": "https://cdn.jsdelivr.net/npm/requete/shared.mjs"
}
}
</script>
<script type="module">
import { create } from 'requete'
const requete = create({ baseURL: 'https://httpbin.org' })
requete.get('/get')
</script>
The following aliases are provided for convenience:
requete.request<D = any>(config: IRequest): Promise<IContext<D>>
requete.get<D = any>(url: string, config?: IRequest): Promise<IContext<D>>
requete.delete<D = any>(url: string, config?: IRequest): Promise<IContext<D>>
requete.head<D = any>(url: string, config?: IRequest): Promise<IContext<D>>
requete.options<D = any>(url: string, config?: IRequest): Promise<IContext<D>>
requete.post<D = any>(url: string, data?: RequestBody, config?: IRequest): Promise<IContext<D>>
requete.put<D = any>(url: string, data?: RequestBody, config?: IRequest): Promise<IContext<D>>
requete.patch<D = any>(url: string, data?: RequestBody, config?: IRequest): Promise<IContext<D>>
Example:
import { create } from 'requete'
const requete = create({ baseURL: 'https://your-api.com/api' })
// Make a GET request for user profile with ID
requete
.get<IUser>('/users/profile?id=123')
.then((r) => r.data)
.catch(console.error)
.finally(() => {
// always executed
})
// or use `config.params` to set url search params
requete.get<IUser>('/users/profile', { params: { id: '123' } })
requete.get<IUser>('/users/profile', { params: 'id=123' })
// Make a POST request for update user profile
requete.post('/users/profile', { id: '123', name: 'Jay Chou' })
// or use `requete.request`
requete.request({
url: '/users/profile',
method: 'POST'
data: { id: '123', name: 'Jay Chou' },
})
requete.delete('/users/profile/123')
requete.put('/users/profile/123', { name: 'Jay Chou' })
requete.use
for add a middleware function to requete. It returns this, so is chainable.
- The calling order of middleware should follow the Onion Model.
like
Koa middleware
. ctx
is the requete context object, typeIContext
. more information in here.next()
must be called asynchronously in middleware- Throwing an exception in middleware will break the middleware execution chain.
- Even if
ctx.ok === false
, there`s no error will be thrown in middleware.
requete
.use(async (ctx, next) => {
const token = getToken()
// throw a `RequestError` if unauthorize
if (!token) ctx.throw('unauthorize')
// set Authorization header
else ctx.set('Authorization', token)
// wait for request responding
await next()
// when unauthorized, re-authenticate.
if (ctx.status === 401) reauthenticate()
})
.use((ctx, next) =>
next().then(() => {
// throw a `RequestError` and break the subsequent execution
if (!ctx.data.some_err_code === '<error_code>') {
ctx.throw('Server Error')
}
})
)
create(config?: RequestConfig)
interface RequestConfig {
baseURL?: string
/** request timeout (ms) */
timeout?: number
/** response body type */
responseType?: 'json' | 'formData' | 'text' | 'blob' | 'arrayBuffer'
/** A string indicating how the request will interact with the browser's cache to set request's cache. */
cache?: RequestCache
/** A string indicating whether credentials will be sent with the request always, never, or only when sent to a same-origin URL. Sets request's credentials. */
credentials?: RequestCredentials
/** A Headers object, an object literal, or an array of two-item arrays to set request's headers. */
headers?: HeadersInit
/** A cryptographic hash of the resource to be fetched by request. Sets request's integrity. */
integrity?: string
/** A boolean to set request's keepalive. */
keepalive?: boolean
/** A string to indicate whether the request will use CORS, or will be restricted to same-origin URLs. Sets request's mode. */
mode?: RequestMode
/** A string indicating whether request follows redirects, results in an error upon encountering a redirect, or returns the redirect (in an opaque fashion). Sets request's redirect. */
redirect?: RequestRedirect
/** A string whose value is a same-origin URL, "about:client", or the empty string, to set request's referrer. */
referrer?: string
/** A referrer policy to set request's referrerPolicy. */
referrerPolicy?: ReferrerPolicy
/** enable logger or set logger level # */
verbose?: boolean | number
/**
* parse json function
* (for transform response)
* @default JSON.parse
*/
toJSON?(body: string): any
}
config.verbose
is used to toggle the logger output.
- set
true
or2
: outputinfo
anderror
level - set
1
: outputerror
level - set
false
or0
or not set: no output
requete.request(config?: IRequest)
interface IRequest extends RequestConfig {
url: string
/**
* A string to set request's method.
* @default GET
*/
method?: Method
/** A string or object to set querystring of url */
params?: string | Record<string, any>
/** request`s body */
data?: RequestBody
/**
* A TimeoutAbortController to set request's signal.
* @default new TimeoutAbortController(timeout)
*/
abort?: TimeoutAbortController | null
/** specify request adapter */
adapter?: Adapter
/** flexible custom field */
custom?: any
}
You can specify request config defaults globally, that will be applied to every request.
And the Requete.defaults
is defined here.
import { Requete } from 'requete'
Requete.defaults.baseURL = 'https://your-api.com'
Requete.defaults.timeout = 60000
Requete.defaults.responseType = 'json'
Requete.defaults.headers = { 'X-Request-Id': 'requete' }
The response for a request is a context object, specifically of type IContext
, which contains the following information.
interface IResponse<Data = any> {
headers: Headers
ok: boolean
redirected: boolean
status: number
statusText: string
type: ResponseType
url: string
data: Data
/** response text when responseType is `json` or `text` */
responseText?: string
}
interface IContext<Data = any> extends IResponse<Data> {
/**
* request config.
* and empty `Headers` object as default
*/
request: IRequest & { headers: Headers }
/**
* set request headers
* *And header names are matched by case-insensitive byte sequence.*
* @throws {RequestError}
*/
set(headerOrName: HeadersInit | string, value?: string | null): this
/**
* Add extra params to `request.url`.
* If there are duplicate keys, then the original key-values will be removed.
*/
params(params: RequestQuery): this
/**
* get `ctx.request.abort`,
* and **create one if not exist**
* @throws {RequestError}
*/
abort(): TimeoutAbortController
/** throw {@link RequestError} */
throw(e: string | Error): void
/**
* Assign to current context
*/
assign(context: Partial<IContext>): void
/**
* Replay current request
* And assign new context to current, with replay`s response
*/
replay(): Promise<void>
}
In middleware, the first argument is ctx
of type IContext
. You can call methods such as ctx.set
, ctx.throw
, ctx.abort
before sending the request (i.e., before the await next()
statement).
Otherwise, if these methods are called in other cases, a RequestError
will be thrown.
set one header of request. And header names are matched by case-insensitive byte sequence.
set multi headers of request.
Add extra params
to request.url
.
If there are duplicate keys, then the original key-values will be removed.
Return the current config.abort
, and create one if not exist
It is used to throw a RequestError
It is used to assign new context object to current. (Object.assign
)
It is used to replay the request in middleware or other case.
After respond, will assign new context to current, with replay`s response,
And will add counts of replay in ctx.request.custom.replay
.
Examples:
const Auth = {
get token() {
return localStorage.getItem('token')
},
set token(value) {
return localStorage.setItem('token', value)
},
authenticate: () =>
requete.post('/authenticate').then((r) => {
Auth.token = r.data.token
}),
}
requete.use(async (ctx, next) => {
ctx.set('Authorization', `Bearer ${Auth.token}`)
await next()
// when unauthorized, re-authenticate
// Maybe causes dead loop if always respond 401
if (ctx.status === 401) {
await Auth.authenticate()
// replay request after re-authenticated.
await ctx.replay()
}
})
RequestError
inherits from Error
, contains the request context information.
It should be noted that all exceptions in requete are RequestError
.
class RequestError extends Error {
name = 'RequestError'
ctx: IContext
constructor(errMsg: string | Error, ctx: IContext)
}
If needed, you can import RequestError
it from requete
import { RequestError } from 'requete'
throw new RequestError('<error message>', ctx)
throw new RequestError(new Error('<error message>'), ctx)
Throw RequestError
in requete middleware
// in requete middleware
ctx.throw('<error message>')
Caught RequeteError
in request
// promise.catch
requete.post('/api').catch((e) => {
console.log(e.name) // "RequestError"
console.log(e.ctx.status) // response status
console.log(e.ctx.headers) // response header
})
// try-catch
try {
await requete.post('/api')
} catch (e) {
console.log(e.name) // "RequestError"
console.log(e.ctx.status) // response status
console.log(e.ctx.headers) // response header
}
it is used to auto-abort requests when timeout, and you can also call abort()
to terminate them at any time. It is implemented based on AbortController.
In the requete configuration, you can add the TimeoutAbortController
through the abort
field.
It should be noted that if you set the timeout
field in config and unset the abort
field, requete
will add the TimeoutAbortController
by default to achieve timeout termination.
If the target browser does not support AbortController
, please add a polyfill before using it.
class TimeoutAbortController {
/** if not supported, it will throw error when `new` */
static readonly supported: boolean
/** timeout ms */
constructor(timeout: number)
get signal(): AbortSignal
abort(reason?: any): void
/** clear setTimeout */
clear(): void
}
import { TimeoutAbortController } from 'requete'
/** By `abort` config */
const controller = new TimeoutAbortController(5000)
requete
.get('https://httpbin.org/delay/10', { abort: controller })
.catch((e) => {
console.error(e) // "canceled"
})
controller.abort('canceled') // you can abort request
/** By `timeout` config */
requete.get('https://httpbin.org/delay/10', { timeout: 5000 })
There are two request adapters in requete: FetchAdapter
, XhrAdapter
.
- In Browser: using
FetchAdapter
as default, andXhrAdapter
is used as a fallback. - In Node.js: using
FetchAdapter
.
Of course, you can also customize which adapter to use by declaring the adapter
field in config.
For example, in browser environment, when obtaining download or upload progress events, you can choose to use the XhrAdapter
. (like Axios)
import requete, { XhrAdapter } from 'requete'
requete.get('/download-or-upload', {
adapter: new XhrAdapter({ onDownloadProgress(e) {}, onUploadProgress(e) {} }),
})
Additionally, requete
also supports custom adapters by inheriting the abstract class Adapter
and implementing the request
method.
abstract class Adapter {
abstract request(ctx: IContext): Promise<IResponse>
}
// CustomAdapter.ts
import { Adapter } from 'requete/adapter'
export class CustomAdapter extends Adapter {
async request(ctx: IContext) {
// do request
return response
}
}
If needed, you can directly import requete/polyfill
. It includes polyfills for Headers
and AbortController
.
requete/polyfill
will determine whether to add polyfills based on the user's browser.
- Headers`s by headers-polyfill
- AbortController`s by abortcontroller-polyfill
In ES Module:
import 'requete/polyfill'
In Browser:
<!-- using jsdelivr -->
<script src="https://cdn.jsdelivr.net/npm/requete/polyfill.umd.min.js"></script>
<!-- using unpkg -->
<script src="https://unpkg.com/requete/polyfill.umd.min.js"></script>