Resreq

version deno land workflow download JavaScript Style Guide

What is resreq?

It is a modern http client, based on fetch, because it is implemented internally using the onion model, so you can use middleware to intercept requests and responses elegantly.

Learn more

Install

Resreq targets modern browsers and Deno

pnpm install resreq

or

import Resreq from 'https://esm.sh/resreq'

If you use it in Node, you need to add some polyfill

import fetch, { Headers, Request, Response } from 'node-fetch'
import AbortController from 'abort-controller'

globalThis.fetch = fetch
globalThis.Headers = Headers
globalThis.Request = Request
globalThis.Response = Response
globalThis.AbortController = AbortController

Documentation

Get Started

import Resreq from 'resreq'

const resreq = new Resreq({
  baseUrl: 'https://example.com',
  responseType: 'json'
})

const res = await resreq.request({
  url: '/api/user',
  method: 'GET',
  params: { foo: 'bar' }
})
console.log(res) // Object

const res = await resreq.get('/api/download', {
  responseType: 'blob'
})
console.log(res) // Blob

Cancel request

const resreq = new Resreq()

const abortController = new AbortController()

resreq.get('https://example.com/api', {
  signal: abortController.signal
}).catch(error => {
  console.log(error) // Abort error
})

abortController.abort() // request abort

Use Middlewares

const resreq = new Resreq({
  baseUrl: 'https://example.com'
})

// Intercepting responses and requests using middleware
resreq.use((next) => async (req) => {
  try {
    console.log(req) // Request can be changed here
    const res = await next(req)
    console.log(res) // Response can be changed here
    return res
  } catch (error) {
    console.log(error) // Catch errors here
    throw error
  }
})

const res = await resreq.get('/api', {
  params: { foo: 'bar' }
})

console.log(res)

API

new Resreq(options?:Options)

Create a resreq instance and configure the global options

const resreq = new Resreq({
  baseUrl: 'https://example.com',
  timeout: 10000,
  responseType: 'json',
  throwHttpError: true,
  onResponseProgress(progress, chunk) {
    console.log(progress, chunk)
  }
})

resreq.request(options?:Options)

Use ''request'' to send the request and configure the options

const resreq = new Resreq({
  baseUrl: 'https://example.com'
})

const res = await resreq.request({
  url: '/api',
  method: 'GET',
  params: { foo: 'bar' },
  throwHttpError: true,
  onResponseProgress(progress, chunk) {
    console.log(progress, chunk)
  }
})

console.log(res)

resreq[method](options?:Options)

Use ''method'' to send the request and configure the options

const resreq = new Resreq({
  baseUrl: 'https://example.com'
})

const res = await resreq.get('/api', {
  params: { foo: 'bar' },
  throwHttpError: true,
  onResponseProgress(progress, chunk) {
    console.log(progress, chunk)
  }
})

console.log(res)

resreq.use(middleware:Middleware)

Rewriting request headers using middleware

import Resreq, { Req } from 'resreq'

const resreq = new Resreq({
  baseUrl: 'https://example.com'
})

resreq.use(next => async req => {
  // Create a new request with Req
  const _req = new Req(req, {
    headers: {
      'X-Custom-Header': 'bar'
    }
  })
  return await next(_req)
})

const res: Response = await resreq.get('/api', {
  headers: {
    'X-Custom-Header': 'foo'
  }
})

console.log(res.headers.get('X-Custom-Header')) // bar

Req(req:Req, init?:ReqInit)

Res(res:Res, init?:ResInit)

In the middleware, use new Req() and new Res() to rewrite the request and response

import Resreq, { Middleware, Req, Res } from 'resreq'

const resreq = new Resreq({
  baseUrl: 'https://example.com'
})

const middleware: Middleware = next => async req => {
  const _req = new Req(req, {
    url: 'http://localhost:3000/mock'
  })
  const res = await next(_req)
  return new Res(res, {
    status: 200,
    statusText: 'mock success'
  })
}

const res: Response = await resreq.get('/api')

console.log(res.status) // 200

Warning Req & Res extends from Request and Response; to create a new request and response in the middleware, use Req & Res

Interfaces

Options

Options extends from the RequestInit type with some additional properties

interface Options extends Omit<RequestInit, 'body'> {
  baseUrl?: string
  url?: string
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD' | 'PATCH'
  params?: Record<string, any>
  body?: BodyInit | Record<string, any>
  meta?: Record<string, any>
  timeout?: number
  responseType?: 'json' | 'arrayBuffer' | 'blob' | 'formData' | 'text' | null | false
  throwHttpError?: boolean
  onResponseProgress?: ProgressCallback
}
  • baseUrl: The url prefix of the request will be concatenated with the url in resreq[method]() to form a complete request address, the default value is ' '
  • url: Request url, the default value is ' '
  • method:Request method, the default value is 'GET'
  • params: The params of a resreq.get request are automatically added to the url via the new URLSearchParams method
  • body: Based on BodyInit, and adding Record<string, any>, which means you can pass object directly, instead of JSON.stringify(object), which will automatically add Content-Type: application/json request headers
  • meta: The extra information that needs to be carried in the request is not really sent to the server, but it can be obtained in the res.meta
  • timeout: Specify the number of milliseconds of time before the request, if the time is exceeded the request will be aborted, the default value is 1000ms
  • responseType: Set how the response will be parsed, if not set or set to false, the Response instance will be returned
  • throwHttpError: If true, a status code outside of 200-299 will throw an error, the default value is false
  • onResponseProgress: The download progress hook, which depends on the ReadableStream API, does not currently work in node

To avoid adding complexity, new Resreq(options) and resreq[method](options), in which 'options' are of the same type

The options defined in new Resreq(options) will take effect globally, resreq[method](options), will override the "global options", except for the following options which will not be overridden

  • headers: The headers defined in the method are merged into the global headers

  • onResponseProgress: Defining onResponseProgress in a method and the global onResponseProgress are both retained.

ReqInit

Options extends from the RequestInit type with some additional properties

interface ReqInit extends Omit<RequestInit, 'body'> {
  url?: string
  method?: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'HEAD' | 'PATCH'
  meta?: Record<string, any>
  timeout?: number
  responseType?: 'json' | 'arrayBuffer' | 'blob' | 'formData' | 'text' | null | false
  throwHttpError?: boolean
  body?: BodyInit | Record<string, any>
  onResponseProgress?: ProgressCallback
}

Note That its 'headers' behave differently than 'Options.headers', which overrides the global headers

ResInit

ResInit extends from the ResponseInit type with some additional properties

interface ResInit extends ResponseInit {
  meta?: Record<string, any>
  timeout?: number
  responseType?: 'json' | 'arrayBuffer' | 'blob' | 'formData' | 'text' | null | false
  throwHttpError?: boolean
  onResponseProgress?: ProgressCallback
}

Middleware

The middleware must call next(req) to return a promise

type Middleware = (next: Next) => (req: Req) => Promise<Res>

Standing on the shoulders of giants

Some of the inspiration for this project came from their.

  • Axios: Promise based HTTP client for the browser and node.js
  • Ky: Tiny & elegant JavaScript HTTP client based on the browser Fetch API
  • Redux: Predictable state container for JavaScript apps
  • Koa: Next generation web framework for node.js

License

This project is licensed under the MIT License - see the LICENSE file for details