Skip to content
/ hypf Public

🀏 Small (2.5kB MINIFIED + GZIPPED & 0 dependencies) and strong-typed HTTP client for Deno, Bun, Node.js, Cloudflare Workers and Browsers.

License

Notifications You must be signed in to change notification settings

fzn0x/hypf

Repository files navigation


GitHub Workflow Status GitHub npm npm JSR Bundle Size Bundle Size GitHub commit activity GitHub last commit


Small (7.2kB minified & 0 dependencies) and type-powered HTTP client for Deno, Bun, Node.js, Cloudflare Workers and Browsers. πŸš€

The most flexible fetch wrapper that allows you to have more than one practice to get things done!

Table of Contents

Get Started

# Node.js
npm install hypf
# Bun
bun install hypf

The idea of this tool is to provide lightweight fetch wrapper for Node.js, Bun:

import hypf from 'hypf'

const hypfRequest = hypf.init('https://jsonplaceholder.typicode.com')

// Example usage of POST method with retry and timeout
const [postErr, postData] = await hypfRequest.post(
  '/posts',
  { retries: 3, timeout: 5000 },
  {
    title: 'foo',
    body: 'bar',
    userId: 1,
  }
)

if (postErr) {
  console.error('POST Error:', postErr)
} else {
  console.log('POST Data:', postData)
}

Cloudflare Workers:

export default {
  async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const hypfInstance = await hypf.init('https://jsonplaceholder.typicode.com')

    const [getErr, getData] = await hypfInstance.get<
      Array<{
        userId: number
        id: number
        title: string
        body: string
      }>
    >('/posts')

    if (getErr) {
      console.error('GET Error:', getErr)
    }

    return Response.json(getData)
  },
}

and Browsers:

<script src="https://unpkg.com/hypf/browser/hyperfetch-browser.min.js"></script>

<script>
  ;(async () => {
    const request = hypf.default.init('https://jsonplaceholder.typicode.com')
  })()
</script>

Error Handling

You can handle errors like Error handling on Golang

const hypfRequest = hypf.init('https://jsonplaceholder.typicode.com')

// Example usage of POST method with retry and timeout
const [postErr, postData] = await hypfRequest.post(
  '/posts',
  { retries: 3, timeout: 5000 },
  {
    title: 'foo',
    body: 'bar',
    userId: 1,
  }
)

if (postErr) {
  console.error('POST Error:', postErr)
} else {
  console.log('POST Data:', postData)
}

or since v0.2.2 throw on error with throwOnError options sets true

const hypfRequest = hypf.init('https://jsonplaceholder.typicode.com')

try {
  const response = await hypfRequest.post(
    '/posts',
    { retries: 3, timeout: 5000, initOptions: { throwOnError: true } },
    {
      title: 'foo',
      body: 'bar',
      userId: 1,
    }
  )

  console.log(response)
} catch (err) {
  console.log(err)
}

Response Cloning

You need to use throwOnError: true to enable Response clone feature.

const res = await hypfRequest.get(
  'https://jsonplaceholder.typicode.com/todos/1',
  {},
  {},
  {
    throwOnError: true,
  }
)

const res2 = res.clone()

console.log(res2)

Request Cloning & Dry Run

Dry Run is supported in hypf, you can calling hypf requests without executing it!

This enables more features in the future and also Request cloning:

const req = await hypfRequest.get(
  'https://jsonplaceholder.typicode.com/todos/1',
  {
    dryRun: true,
  },
  {},
  {
    throwOnError: true,
  }
)

const req2 = req.clone()

console.log(req2)

Hooks

Hooks is supported and expected to not modifying the original result by design.

const hooks = {
  preRequest: (url, options) => {
    console.log(`Preparing to send request to: ${url}`)
    // You can perform actions before the request here
  },
  postRequest: (url, options, data, response) => {
    console.log(`Request to ${url} completed with status: ${response?.[0] ? 'error' : 'success'}`)
    // You can perform actions after the request here, including handling errors
  },
}

const requestWithHooks = hypf.init('https://jsonplaceholder.typicode.com', { hooks })

// Example usage of POST method with retry and timeout
const [postErr, postData] = await requestWithHooks.post(
  '/posts',
  { retries: 3, timeout: 5000 },
  {
    title: 'foo',
    body: 'bar',
    userId: 1,
  }
)

List of Hooks:

export interface Hooks {
  preRequest?: (url: string, options: RequestOptions) => void
  postRequest?: <T, U>(
    url: string,
    options: RequestOptions,
    data?: T,
    response?: [Error | null, U]
  ) => void
  preRetry?: (url: string, options: RequestOptions, retryCount: number, retryLeft: number) => void
  postRetry?: <T, U>(
    url: string,
    options: RequestOptions,
    data?: T,
    response?: [Error | null, U],
    retryCount?: number,
    retryLeft?: number
  ) => void
  preTimeout?: (url: string, options: RequestOptions) => void
  postTimeout?: (url: string, options: RequestOptions) => void
}

Retry Mechanism

You can retry your request once it's failed!

const [postErr, postData] = await requestWithHooks.post(
  '/posts',
  { retries: 3, timeout: 5000 },
  {
    title: 'foo',
    body: 'bar',
    userId: 1,
  }
)

Jitter and backoff also supported. 😎

const [postErr, postData] = await requestWithHooks.post(
  '/posts',
  { retries: 3, timeout: 5000, jitter: true }, // false `jitter` to use backoff
  {
    title: 'foo',
    body: 'bar',
    userId: 1,
  }
)

You can modify backoff and jitter factor as well.

const [postErr, postData] = await requestWithHooks.post(
  '/posts',
  { retries: 3, timeout: 5000, jitter: true, jitterFactor: 10000 }, // false `jitter` to use backoff
  {
    title: 'foo',
    body: 'bar',
    userId: 1,
  }
)

// or backoff

const [postErr, postData] = await requestWithHooks.post(
  '/posts',
  { retries: 3, timeout: 5000, jitter: false, backoffFactor: 10000 }, // false `jitter` to use backoff
  {
    title: 'foo',
    body: 'bar',
    userId: 1,
  }
)

Retry on timeout also supported.

const [postErr, postData] = await requestWithHooks.post(
  '/posts',
  { retries: 3, timeout: 5000, retryOnTimeout: true },
  {
    title: 'foo',
    body: 'bar',
    userId: 1,
  }
)

Infer Response Types

const [getErr, getData] = await hypfRequest.get<
  Array<{
    userId: number
    id: number
    title: string
    body: string
  }>
>('/posts', {
  retries: 3,
  timeout: 5000,
})

getData?.[0]?.id // number | undefined

URLSearchParams

const [getErr, getData] = await hypfRequest.get('/posts', {
  retries: 3,
  timeout: 5000,
  params: {
    id: 1,
  },
}) // /posts?id=1

Form Data

Example usecase for Upload File:

export async function postImportFile(formData: FormData) {
  const [postErr, postData] = await hypfRequest.post(`/api/upload-file/import`, {
    body: formData,
  })

  if (postErr) {
    throw postErr
  }

  return postData
}

AbortController

We expose abort controller, you can cancel next request anytime.

// DELETE will not work if you uncomment this
const controller = requestWithHooks.getAbortController()

controller.abort()

// Example usage of DELETE method with retry and timeout
const [deleteErr, deleteData] = await requestWithHooks.delete('/posts/1', {
  retries: 3,
  timeout: 5000,
})

if (deleteErr) {
  console.error('DELETE Error:', deleteErr)
} else {
  console.log('DELETE Data:', deleteData)
}

Debugging

Debugging the library is possible but limited. You can pass true to debug option:

const requestWithHooks = hypf.init('https://jsonplaceholder.typicode.com', { debug: true })

This is designed to be useful to track an issue. Submit a PR to debug more areas in the code!

Acknowledgements

Hyperfetch is highly inspired by Hono, Got, Ky and Axios

License

Hyperfetch is MIT-licensed and Open Source Software by fzn0x and contributors from Hono and the Hyperfetch community: