ky README

The project's README from github.com/sindresorhus/ky. As one markdown file: README.md.


ky




Ky is a tiny and elegant HTTP client based on the Fetch API

Coverage Status

Ky targets modern browsers, Node.js, Bun, and Deno.

It's just a tiny package with no dependencies.

Benefits over plain fetch

Install

npm install ky

[!NOTE] This readme is for the next version of Ky. For the current version, click here.

CDN

Usage

import ky from 'ky';

const json = await ky.post('https://example.com', {json: {foo: true}}).json();

console.log(json);
//=> {data: '🦄'}

With plain fetch, it would be:

class HTTPError extends Error {}

const response = await fetch('https://example.com', {
	method: 'POST',
	body: JSON.stringify({foo: true}),
	headers: {
		'content-type': 'application/json'
	}
});

if (!response.ok) {
	throw new HTTPError(`Fetch error: ${response.statusText}`);
}

const json = await response.json();

console.log(json);
//=> {data: '🦄'}

If you are using Deno, import Ky from a URL. For example, using a CDN:

import ky from 'https://esm.sh/ky';

API

ky(input, options?)

The input and options are the same as fetch, with additional options available (see below).

Returns a Response object with Body methods added for convenience. So you can, for example, call ky.get(input).json() directly without having to await the Response first. When called like that, an appropriate Accept header will be set depending on the body method used. Unlike the Body methods of window.fetch, these will throw an HTTPError if the response status is not in the range of 200...299. Note that .json() will throw if the body is empty, as it cannot be parsed. However, you can use the parseJson option to add custom handling for empty bodies.

Available body shortcuts: .json(), .text(), .formData(), .arrayBuffer(), .blob(), and .bytes(). The .bytes() shortcut is only present when the runtime supports Response.prototype.bytes().

import ky from 'ky';

const user = await ky('/api/user').json();

console.log(user);

⌨️ TypeScript: Accepts an optional type parameter, which defaults to unknown, and is passed through to the return type of .json().

import ky from 'ky';

// user1 is unknown
const user1 = await ky('/api/users/1').json();
// user2 is a User
const user2 = await ky<User>('/api/users/2').json();
// user3 is a User
const user3 = await ky('/api/users/3').json<User>();

console.log([user1, user2, user3]);

You can also get the response body as JSON and validate it with a Standard Schema compatible validator (for example, Zod 3.24+). This throws a SchemaValidationError when validation fails.

import ky, {SchemaValidationError} from 'ky';
import {z} from 'zod';

const userSchema = z.object({name: z.string()});

try {
	const user = await ky('/api/user').json(userSchema);
	console.log(user.name);
} catch (error) {
	if (error instanceof SchemaValidationError) {
		console.error(error.issues);
	}
}
// Get raw bytes (when supported by the runtime)
const bytes = await ky('/api/file').bytes();
console.log(bytes instanceof Uint8Array);

ky.get(input, options?)

ky.post(input, options?)

ky.put(input, options?)

ky.patch(input, options?)

ky.head(input, options?)

ky.delete(input, options?)

ky.query(input, options?)

Sets options.method to the method name and makes a request.

⌨️ TypeScript: Accepts an optional type parameter for use with JSON responses (see ky()).

input

Type: string | URL | Request

Same as fetch input.

When using a Request instance as input, the prefix and baseUrl options are ignored. The searchParams option still applies.

options

Type: object

Same as fetch options, plus the following additional options:

method

Type: string
Default: 'get'

HTTP method used to make the request.

Internally, the standard methods (GET, POST, PUT, PATCH, HEAD, DELETE, and QUERY) are uppercased in order to avoid server errors due to case sensitivity.

json

Type: object and any other value accepted by JSON.stringify()

Shortcut for sending JSON. Use this instead of the body option. Accepts any plain object or value, which will be stringified using JSON.stringify() and sent in the body with the correct header set.

The Content-Type header is set to application/json unless you set a Content-Type in the headers option, which always takes precedence. A Content-Type on a Request input is replaced, because the Request constructor sets one automatically from its body.

searchParams

Type: string | object<string, string | number | boolean | undefined> | Array<Array<string | number | boolean>> | URLSearchParams
Default: ''

Search parameters to include in the request URL. Setting this will merge with any existing search parameters in the input URL.

Accepts any value supported by URLSearchParams().

When passing an object, setting a value to undefined deletes the parameter, including from the input URL, even when a later option layer adds the parameter again. null values are preserved and converted to the string 'null'.

When input is a Request with a body, the body is sent as a stream, which requires request stream support and HTTP/2 for HTTPS connections (in Chromium-based browsers). In environments without request stream support, when keepalive is true, or when the effective mode is 'no-cors', the inherited body is dropped. A compatible body passed explicitly with the body option is still used.

baseUrl

Type: string | URL

A base URL to resolve the input against. When the input (after applying the prefix option) is only a relative URL, such as 'users', '/users', or '//my-site.com', it will be resolved against the baseUrl to determine the destination of the request. Otherwise, the input is absolute, such as 'https://my-site.com', and it will bypass the baseUrl.

Useful when used with ky.extend() to create niche-specific Ky instances.

If the baseUrl itself is relative, it will be resolved against the environment's base URL, such as document.baseURI in browsers or location.href in Deno (see the --location flag).

Tip: When setting a baseUrl that has a path, we recommend that it include a trailing slash /, as in '/api/' rather than /api. This ensures more intuitive behavior for page-relative input URLs, such as 'users' or './users', where they will extend from the full path of the baseUrl rather than replacing its last path segment.

import ky from 'ky';

// On https://example.com

const response = await ky('users', {baseUrl: '/api/'});
//=> 'https://example.com/api/users'

const response = await ky('/users', {baseUrl: '/api/'});
//=> 'https://example.com/users'

See FAQ: baseUrl vs prefix

prefix

Type: string | URL

A prefix to prepend to the input before making the request (and before it is resolved against the baseUrl). It can be any valid path or URL, either relative or absolute. A trailing slash / is optional and will be added automatically, if needed, when it is joined with input. Only takes effect when input is a string.

Useful when used with ky.extend() to create niche-specific Ky instances.

In most cases, you should use the baseUrl option instead, as it is more consistent with web standards. However, prefix is useful if you want origin-relative input URLs, such as /users, to be treated as if they were page-relative. In other words, the leading slash of the input will essentially be ignored, because the prefix will become part of the input before URL resolution happens.

See FAQ: baseUrl vs prefix

import ky from 'ky';

// On https://example.com

const response = await ky('users', {prefix: '/api/'});
//=> 'https://example.com/api/users'

const response = await ky('/users', {prefix: '/api/'});
//=> 'https://example.com/api/users'

Notes:

retry

Type: object | number
Default:

Controls retry behavior. Each field is documented individually below.

If retry is a number, it will be used as limit and other defaults will remain in place.

retry.limit, including numeric shorthand, must be a finite, non-negative integer.

Network errors (e.g., DNS failures, connection refused, offline) are automatically retried for retriable methods. Only errors recognized as network errors are retried; other errors (e.g., programming bugs) are thrown immediately. Use shouldRetry to customize this behavior.

413 Payload Too Large is only retried when the response includes a retry timing header.

When the response status is contained in both statusCodes and afterStatusCodes, Ky uses retry timing headers to choose the retry delay. Retry-After may provide a delay in seconds or an HTTP-date. If Retry-After is missing, Ky falls back to rate-limit timing headers (RateLimit-Reset, X-RateLimit-Retry-After, X-RateLimit-Reset, and X-Rate-Limit-Reset). Numeric Retry-After and X-RateLimit-Retry-After values are interpreted as delay seconds. Numeric RateLimit-Reset, X-RateLimit-Reset, and X-Rate-Limit-Reset values may also be interpreted as current-era Unix timestamps. If the status code is not in afterStatusCodes, retry timing headers will be ignored.

If the retry delay from a retry timing header is greater than maxRetryAfter, Ky will use maxRetryAfter.

The backoffLimit option is the upper limit of the delay per retry in milliseconds. To clamp the delay, set backoffLimit to 1000, for example. By default, the delay is calculated with 0.3 * (2 ** (attemptCount - 1)) * 1000. The delay increases exponentially.

The delay option can be used to change how the delay between retries is calculated. The function receives one parameter, the attempt count, starting at 1, and must return the delay in milliseconds.

The jitter option adds random jitter to retry delays to prevent thundering herd problems. When many clients retry simultaneously (e.g., after hitting a rate limit), they can overwhelm the server again. Jitter adds randomness to break this synchronization. Set to true to use full jitter, which randomizes the delay between 0 and the computed delay. Alternatively, pass a function to implement custom jitter strategies.

Note: Jitter is not applied when the server provides a retry timing header, as the server's explicit timing should be respected.

The retryOnTimeout option determines whether to retry when a request times out before a response is returned. By default, retries are not triggered following a timeout. Timeouts while reading a response body through Ky shortcut methods are not retried because the response has already been received.

The shouldRetry option provides custom retry logic that takes precedence over the default retry checks (retryOnTimeout, status code checks, etc.) for retriable methods. It is only called after the retry limit and method checks pass.

Note: This is different from the beforeRetry hook:

The function receives a state object with the error and retry count (starts at 1 for the first retry), and should return:

General example

import ky from 'ky';

const json = await ky('https://example.com', {
	retry: {
		limit: 10,
		methods: ['get'],
		statusCodes: [500],
		backoffLimit: 3000
	}
}).json();

Retrying on timeout:

import ky from 'ky';

const json = await ky('https://example.com', {
	timeout: 5000,
	retry: {
		limit: 3,
		retryOnTimeout: true
	}
}).json();

Using jitter to prevent thundering herd:

import ky from 'ky';

const json = await ky('https://example.com', {
	retry: {
		limit: 5,

		// Full jitter (randomizes delay between 0 and computed value)
		jitter: true

		// Percentage jitter (80-120% of delay)
		// jitter: delay => delay * (0.8 + Math.random() * 0.4)

		// Absolute jitter (±100ms)
		// jitter: delay => delay + (Math.random() * 200 - 100)
	}
}).json();

Custom retry logic:

import ky, {HTTPError} from 'ky';

const json = await ky('https://example.com', {
	retry: {
		limit: 3,
		shouldRetry: ({error, retryCount}) => {
			// Retry on specific business logic errors from API
			if (error instanceof HTTPError) {
				const status = error.response.status;

				// Retry on 429 (rate limit) but only for first 2 attempts
				if (status === 429 && retryCount <= 2) {
					return true;
				}

				// Don't retry on 4xx errors except rate limits
				if (status >= 400 && status < 500) {
					return false;
				}
			}

			// Use default retry logic for other errors
			return undefined;
		}
	}
}).json();

[!NOTE] When retries are enabled, Ky clones the request body before each attempt using tee(), which buffers the entire ReadableStream in memory. Set retry: {limit: 0} if you're uploading large streaming bodies and don't need retries.

[!NOTE] Chromium-based browsers automatically retry 408 Request Timeout responses at the network layer for keep-alive connections. This means requests may be retried by both the browser and ky. If you want to avoid duplicate retries, you can either set keepalive: false in your request options (though this may impact performance for multiple requests) or remove 408 from the retry status codes.

timeout

Type: number | false
Default: 10000

Per-attempt timeout in milliseconds for getting a response, applied independently to each retry. Ky shortcut methods also use this value as a separate timeout for reading the response body. Cannot be greater than 2147483647. See also totalTimeout.

If set to false, there will be no per-attempt timeout.

totalTimeout

Type: number | false
Default: false

Overall timeout in milliseconds for the entire operation, including retries and delays. Throws a TimeoutError if exceeded. Cannot be greater than 2147483647.

beforeError hooks run after an error is produced and are not bounded by totalTimeout.

If set to false or not specified, there is no overall timeout.

import ky from 'ky';

// Each attempt gets 5s, but the whole operation must complete within 30s
const json = await ky('https://example.com', {
	timeout: 5000,
	totalTimeout: 30_000,
	retry: {
		limit: 3,
		retryOnTimeout: true,
	}
}).json();
hooks

Type: object<string, Function[]>
Default: {init: [], beforeRequest: [], beforeRetry: [], beforeError: [], afterResponse: []}

Hooks allow modifications during the request lifecycle. Hook functions may be async and are run serially, unless otherwise noted.

hooks.init

Type: Function[]
Default: []

This hook enables you to modify the options before they are used to construct the request. The hook function receives the mutable options object and can modify it in place. You could, for example, modify searchParams, headers, or json here. The headers option is always a plain object with lowercase names, where a header removed with undefined keeps an undefined value.

Unlike other hooks, init hooks are synchronous. Any error thrown will propagate synchronously and will not be caught by beforeError hooks.

A common use case is to add a search parameter to every request:

import ky from 'ky';

const api = ky.extend({
	hooks: {
		init: [
			options => {
				options.searchParams = {apiKey: getApiKey()};
			},
		],
	},
});

const response = await api.get('https://example.com/api/users');
// URL: https://example.com/api/users?apiKey=123
hooks.beforeRequest

Type: Function[]
Default: []

This hook enables you to modify the request right before it is sent. Ky will make no further changes to the request after this. The hook function receives a state object with the normalized request, options, and retry count. You could, for e

ky (sindresorhus/ky)

Searching Database…

17.1K ·GitHub
这是什么东西?Ky 是一个基于 Fetch API 构建的轻巧优雅的 JavaScript HTTP 客户端。它保留了 fetch 的使用模式,同时增加了重试、超时、JSON 处理、进度事件和钩子功能——而且是零依赖的。
它和直接用 fetch 有什么区别?它在 fetch 外面包了一层更友好的 API:提供 ky.post() 这类方法快捷方式,非 2xx 的响应会直接抛出错误,还支持自动重试、JSON 和超时选项,以及带自定义默认配置、可以复用的实例。
我怎么把它加到项目里?用 'npm install ky' 从 npm 安装(需要 Node.js 22+)。在浏览器里,可以从 jsdelivr、unpkg 或 esm.sh 这类 CDN 加载;在 Deno 里,直接从 CDN 地址导入即可。
它自己会带别的依赖吗?不会——它刻意做成一个极小的独立包,完全没有任何依赖。你只需要一个自带 Fetch API 的运行环境。
用之前有什么需要知道的坑吗?状态码不在 200-299 之间的响应会抛出 HTTPError,.json() 在响应体为空时会报错(这种情况要用 parseJson 选项),另外每次请求默认 10 秒就会超时。
它能在浏览器之外用吗,比如 Node 或 Deno?可以——Ky 面向现代浏览器、Node.js、Bun 和 Deno,而且同一套选项 API 在这些环境里都通用。
自动重试是怎么工作的?默认情况下,GET、PUT、HEAD、DELETE 这类方法的请求最多重试 2 次,而 POST 和 PATCH 不会重试,重试间隔从 0.3 秒开始按指数递增。Ky 还会遵循 Retry-After 和限流相关的响应头,并且会对 DNS 解析失败这类网络错误进行重试。
它能按 schema 校验 JSON 响应吗?可以——.json() 接受任何兼容 Standard Schema 的校验器,比如 Zod 3.24+ 或 Valibot,当响应体不符合结构时会抛出 SchemaValidationError。
我能为自己的 API 创建一个预先配置好的客户端吗?用 ky.extend() 可以创建一个带自定义默认配置的实例,再设置 baseUrl,这样像 'users' 这样的相对路径就会基于你的 API 根地址来解析。
hooks 能用来做什么?钩子会在五个生命周期节点上按顺序执行(支持异步)——init、beforeRequest、beforeRetry、beforeError 和 afterResponse——很适合用来添加鉴权请求头、记录日志或对响应做后处理。
Size
1.8 MB
Stars
17.1K
Age
8y
Commits · 90 Days