🌳 Tiny & elegant JavaScript HTTP client based on the browser Fetch API
Ky is a tiny and elegant HTTP client based on the browser Fetch API
Ky targets modern browsers and Deno. For older browsers, you will need to transpile and use a
fetchpolyfill and
globalThispolyfill. For Node.js, check out Got. For isomorphic needs (like SSR), check out
ky-universal.
It's just a tiny file with no dependencies.
fetch
ky.post())
$ npm install ky
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://cdn.skypack.dev/ky?dts';
The
inputand
optionsare the same as
fetch, with some exceptions:
credentialsoption is
same-originby default, which is the default in the spec too, but not all browsers have caught up yet.
Responseobject with
Bodymethods added for convenience. So you can, for example, call
ky.get(input).json()directly without having to await the
Responsefirst. When called like that, an appropriate
Acceptheader will be set depending on the body method used. Unlike the
Bodymethods of
window.Fetch; these will throw an
HTTPErrorif the response status is not in the range of
200...299. Also,
.json()will return an empty string if the response status is
204instead of throwing a parse error due to an empty body.
Sets
options.methodto the method name and makes a request.
When using a
Requestinstance as
input, any URL altering options (such as
prefixUrl) will be ignored.
Type:
object
Type:
string\ Default:
'get'
HTTP method used to make the request.
Internally, the standard methods (
GET,
POST,
PUT,
PATCH,
HEADand
DELETE) are uppercased in order to avoid server errors due to case sensitivity.
Type:
objectand any other value accepted by
JSON.stringify()
Shortcut for sending JSON. Use this instead of the
bodyoption. Accepts any plain object or value, which will be
JSON.stringify()'d and sent in the body with the correct header set.
Type:
string | object | Array> | URLSearchParams\ Default:
''
Search parameters to include in the request URL. Setting this will override all existing search parameters in the input URL.
Accepts any value supported by
URLSearchParams().
Type:
string | URL
A prefix to prepend to the
inputURL when making the request. It can be any valid 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
inputis a string. The
inputargument cannot start with a slash
/when using this option.
ky.extend()to create niche-specific Ky-instances.
import ky from 'ky';// On https://example.com
const response = await ky('unicorn', {prefixUrl: '/api'}); //=> 'https://example.com/api/unicorn'
const response2 = await ky('unicorn', {prefixUrl: 'https://cats.com'}); //=> 'https://cats.com/unicorn'
Notes: - After
prefixUrland
inputare joined, the result is resolved against the base URL of the page (if any). - Leading slashes in
inputare disallowed when using this option to enforce consistency and avoid confusion about how the
inputURL is handled, given that
inputwill not follow the normal URL resolution rules when
prefixUrlis being used, which changes the meaning of a leading slash.
Type:
object | number\ Default: -
limit:
2-
methods:
get
put
head
delete
options
trace-
statusCodes:
408
413
429
500
502
503
504-
maxRetryAfter:
undefined
An object representing
limit,
methods,
statusCodesand
maxRetryAfterfields for maximum retry count, allowed methods, allowed status codes and maximum
Retry-Aftertime.
If
retryis a number, it will be used as
limitand other defaults will remain in place.
If
maxRetryAfteris set to
undefined, it will use
options.timeout. If
Retry-Afterheader is greater than
maxRetryAfter, it will cancel the request.
Delays between retries is calculated with the function
0.3 * (2 ** (retry - 1)) * 1000, where
retryis the attempt number (starts from 1).
import ky from 'ky';const json = await ky('https://example.com', { retry: { limit: 10, methods: ['get'], statusCodes: [413] } }).json();
Type:
number | false\ Default:
10000
Timeout in milliseconds for getting a response. Can not be greater than 2147483647. If set to
false, there will be no timeout.
Type:
object\ Default:
{beforeRequest: [], beforeRetry: [], afterResponse: []}
Hooks allow modifications during the request lifecycle. Hook functions may be async and are run serially.
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
requestand
optionsas arguments. You could, for example, modify the
request.headershere.
Requestto replace the outgoing request, or return a
Responseto completely avoid making an HTTP request. This can be used to mock a request, check an internal cache, etc. An important consideration when returning a request or response from this hook is that any remaining
beforeRequesthooks will be skipped, so you may want to only return them from the last hook.
import ky from 'ky';const api = ky.extend({ hooks: { beforeRequest: [ request => { request.headers.set('X-Requested-With', 'ky'); } ] } });
const response = await api.get('https://example.com/api/users');
Type:
Function[]\ Default:
[]
This hook enables you to modify the request right before retry. Ky will make no further changes to the request after this. The hook function receives an object with the normalized request and options, an error instance, and the retry count. You could, for example, modify
request.headershere.
If the request received a response, the error will be of type
HTTPErrorand the
Responseobject will be available at
error.response. Be aware that some types of errors, such as network errors, inherently mean that a response was not received. In that case, the error will not be an instance of
HTTPError.
You can prevent Ky from retrying the request by throwing an error. Ky will not handle it in any way and the error will be propagated to the request initiator. The rest of the
beforeRetryhooks will not be called in this case. Alternatively, you can return the
ky.stopsymbol to do the same thing but without propagating an error (this has some limitations, see
ky.stopdocs for details).
import ky from 'ky';const response = await ky('https://example.com', { hooks: { beforeRetry: [ async ({request, options, error, retryCount}) => { const token = await ky('https://example.com/refresh-token'); request.headers.set('Authorization',
token ${token}
); } ] } });
Type:
Function[]\ Default:
[]
This hook enables you to read and optionally modify the response. The hook function receives normalized request, options, and a clone of the response as arguments. The return value of the hook function will be used by Ky as the response object if it's an instance of
Response.
import ky from 'ky';const response = await ky('https://example.com', { hooks: { afterResponse: [ (_request, _options, response) => { // You could do something with the response, for example, logging. log(response);
// Or return a `Response` instance to overwrite the response. return new Response('A different response', {status: 200}); }, // Or retry with a fresh token on a 403 error async (request, options, response) => { if (response.status === 403) { // Get a fresh token const token = await ky('https://example.com/token').text(); // Retry with the token request.headers.set('Authorization', `token ${token}`); return ky(request); } } ] }
});
Type:
boolean\ Default:
true
Throw an
HTTPErrorwhen, after following redirects, the response has a non-2xx status code. To also throw for redirects instead of following them, set the
redirectoption to
'manual'.
Setting this to
falsemay be useful if you are checking for resource availability and are expecting error responses.
Type:
Function
Download progress event handler.
The function receives a
progressand
chunkargument: - The
progressobject contains the following elements:
percent,
transferredBytesand
totalBytes. If it's not possible to retrieve the body size,
totalByteswill be
0. - The
chunkargument is an instance of
Uint8Array. It's empty for the first call.
import ky from 'ky';const response = await ky('https://example.com', { onDownloadProgress: (progress, chunk) => { // Example output: //
0% - 0 of 1271 bytes
//100% - 1271 of 1271 bytes
console.log(${progress.percent * 100}% - ${progress.transferredBytes} of ${progress.totalBytes} bytes
); } });
Type:
Function\ Default:
JSON.parse()
User-defined JSON-parsing function.
Use-cases: 1. Parse JSON via the
bournepackage to protect from prototype pollution. 2. Parse JSON with
reviveroption of
JSON.parse().
import ky from 'ky'; import bourne from '@hapijs/bourne';const json = await ky('https://example.com', { parseJson: text => bourne(text) }).json();
Type:
Function\ Default:
fetch
User-defined
fetchfunction. Has to be fully compatible with the Fetch API standard.
Use-cases: 1. Use custom
fetchimplementations like
isomorphic-unfetch. 2. Use the
fetchwrapper function provided by some frameworks that use server-side rendering (SSR).
import ky from 'ky'; import fetch from 'isomorphic-unfetch';const json = await ky('https://example.com', {fetch}).json();
Create a new
kyinstance with some defaults overridden with your own.
In contrast to
ky.create(),
ky.extend()inherits defaults from its parent.
You can pass headers as a
Headersinstance or a plain object.
You can remove a header with
.extend()by passing the header with an
undefinedvalue. Passing
undefinedas a string removes the header only if it comes from a
Headersinstance.
import ky from 'ky';const url = 'https://sindresorhus.com';
const original = ky.create({ headers: { rainbow: 'rainbow', unicorn: 'unicorn' } });
const extended = original.extend({ headers: { rainbow: undefined } });
const response = await extended(url).json();
console.log('rainbow' in response); //=> false
console.log('unicorn' in response); //=> true
Create a new Ky instance with complete new defaults.
import ky from 'ky';// On https://my-site.com
const api = ky.create({prefixUrl: 'https://example.com/api'});
const response = await api.get('users/123'); //=> 'https://example.com/api/users/123'
const response = await api.get('/status', {prefixUrl: ''}); //=> 'https://my-site.com/status'
Type:
object
Exposed for
instanceofchecks. The error has a
responseproperty with the
Responseobject,
requestproperty with the
Requestobject, and
optionsproperty with normalized options (either passed to
kywhen creating an instance with
ky.create()or directly when performing the request).
The error thrown when the request times out. It has a
requestproperty with the
Requestobject.
A
Symbolthat can be returned by a
beforeRetryhook to stop the retry. This will also short circuit the remaining
beforeRetryhooks.
Note: Returning this symbol makes Ky abort and return with an
undefinedresponse. Be sure to check for a response before accessing any properties on it or use optional chaining. It is also incompatible with body methods, such as
.json()or
.text(), because there is no response to parse. In general, we recommend throwing an error instead of returning this symbol, as that will cause Ky to abort and then throw, which avoids these limitations.
A valid use-case for
ky.stopis to prevent retries when making requests for side effects, where the returned data is not important. For example, logging client activity to the server.
import ky from 'ky';const options = { hooks: { beforeRetry: [ async ({request, options, error, retryCount}) => { const shouldStopRetry = await ky('https://example.com/api'); if (shouldStopRetry) { return ky.stop; } } ] } };
// Note that response will be
undefined
in caseky.stop
is returned. const response = await ky.post('https://example.com', options);// Using
.text()
or other body methods is not suppported. const text = await ky('https://example.com', options).text();
Sending form data in Ky is identical to
fetch. Just pass a
FormDatainstance to the
bodyoption. The
Content-Typeheader will be automatically set to
multipart/form-data.
import ky from 'ky';//
multipart/form-data
const formData = new FormData(); formData.append('food', 'fries'); formData.append('drink', 'icetea');const response = await ky.post(url, {body: formData});
If you want to send the data in
application/x-www-form-urlencodedformat, you will need to encode the data with
URLSearchParams.
import ky from 'ky';//
application/x-www-form-urlencoded
const searchParams = new URLSearchParams(); searchParams.set('food', 'fries'); searchParams.set('drink', 'icetea');const response = await ky.post(url, {body: searchParams});
Fetch (and hence Ky) has built-in support for request cancellation through the
AbortControllerAPI. Read more.
Example:
import ky from 'ky';const controller = new AbortController(); const {signal} = controller;
setTimeout(() => { controller.abort(); }, 5000);
try { console.log(await ky(url, {signal}).text()); } catch (error) { if (error.name === 'AbortError') { console.log('Fetch aborted'); } else { console.error('Fetch error:', error); } }
ky-universal.
ky-universal.
Either use a test runner that can run in the browser, like Mocha, or use AVA with
ky-universal. Read more.
index.jsfile in this repo somewhere, for example, to your website server, or use a CDN version. Then import the file.
got
See my answer here. Got is maintained by the same people as Ky.
axios?
See my answer here.
r2?
See my answer in #10.
kymean?
It's just a random short npm package name I managed to get. It does, however, have a meaning in Japanese:
A form of text-able slang, KY is an abbreviation for 空気読めない (kuuki yomenai), which literally translates into “cannot read the air.” It's a phrase applied to someone who misses the implied meaning.
The latest version of Chrome, Firefox, and Safari.
Polyfill the needed browser globals or just use
ky-universal.