ky

repository·main·Indexed 12 days ago

https://github.com/sindresorhus/ky

A tiny, elegant, and dependency-free HTTP client based on the Fetch API. Version 2.0.2 provides simplified JSON handling, automatic retries, timeout support, and lifecycle hooks for modern browsers, Node.js, Bun, and Deno.

Tokens
14.5K
Snippets
52
Records
58
Agent score
47%

What's inside ky

  1. Benefits of using ky over plain fetch

    main

    Ky provides several improvements over the standard Fetch API:

    • Simpler API: Reduced boilerplate for common tasks.
    • Method shortcuts: Direct access to HTTP methods like ky.post(), ky.get(), etc.
    • Automatic Error Handling: Treats non-2xx status codes as errors (after redirects).
    • Retries: Automatically retries failed requests.
    • JSON support: A dedicated json option for request bodies and improved .json() response parsing.
    • Timeout support: Built-in support for request timeouts.
    • Progress tracking: Support for upload and download progress.
    • Base URL: Option to set a base URL for all requests in an instance.
    • Custom Instances: Create instances with custom default options.
    • Hooks: Lifecycle hooks for intercepting requests and responses.
    • Response Validation: Integration with Standard Schema (e.g., Zod, Valibot).
    • TypeScript improvements: .json() supports generics and defaults to unknown instead of any.
  2. Configure `baseUrl` vs `prefix` for URL resolution

    main

    When creating a Ky instance, you can specify how input URLs are resolved against a base path using either baseUrl or prefix.

    • baseUrl: Follows standard URL resolution rules (like new URL(input, baseUrl)). If the input starts with a leading slash /, it is treated as origin-root, overriding any path in the base URL.
    • prefix: Performs plain string joining. It strips leading slashes from the input, so the input is always appended to the prefix.

    Use baseUrl for almost all cases. Use prefix only when you want origin-relative inputs like /users to be treated as page-relative.

    // On https://example.com
    
    // baseUrl: standard URL resolution
    ky('users',  {baseUrl: '/api/'});
    //=> https://example.com/api/users
    
    ky('/users', {baseUrl: '/api/'});
    //=> https://example.com/users  ← leading slash wins
    
    
    // prefix: always appends
    ky('users',  {prefix: '/api'});
    //=> https://example.com/api/users
    
    ky('/users', {prefix: '/api'});
    //=> https://example.com/api/users  ← leading slash ignored
  3. Use ky hooks to modify the request lifecycle

    main

    Hooks allow you to intercept and modify requests, responses, or errors at various stages of the lifecycle. Hooks can be asynchronous and are run serially.

    Available hook types:

    • init: Modify options before the request is constructed (synchronous).
    • beforeRequest: Modify the Request object right before it is sent.
    • beforeRetry: Modify the Request or return a Response right before a retry attempt.
    • beforeError: Modify an error before it is thrown.
    • afterResponse: Modify the Response or trigger a retry via ky.retry().
  4. Handle Ky errors

    main

    Ky provides a hierarchy of error classes. You can use instanceof KyError or the isKyError() type guard to identify errors originating from Ky's HTTP lifecycle.

    Note: SchemaValidationError is not a KyError because it represents a failure in user-provided schema validation, not a failure in the HTTP lifecycle.

    import ky, {isKyError} from 'ky';
    
    try {
    	await ky('https://example.com').json();
    } catch (error) {
    	if (isKyError(error)) {
    		console.log('Ky error:', error.message);
    	}
    }
  5. Implement token refresh on 401 responses

    main

    You can automate token refreshing by combining the retry option with the beforeRetry hook. Configure retry to include 401 in statusCodes, then use beforeRetry to fetch a new token and update the request headers.

    const api = ky.create({
    	retry: {statusCodes: [401]},
    	hooks: {
    		beforeRetry: [
    			async ({request}) => {
    				const token = await refreshToken();
    				request.headers.set('Authorization', `Bearer ${token}`);
    			}
    		]
    	}
    });
  6. Configure Proxy support in Node.js

    main

    Native Proxy Support (Node.js 24.5+)

    Use environment variables or the --use-env-proxy CLI flag.

    • HTTP_PROXY / http_proxy: Proxy URL for HTTP requests.
    • HTTPS_PROXY / https_proxy: Proxy URL for HTTPS requests.
    • NO_PROXY / no_proxy: Comma-separated list of hosts to bypass.

    Using ProxyAgent (undici)

    For granular control, use ProxyAgent or EnvHttpProxyAgent from undici via the dispatcher option.

    import ky from 'ky';
    import {ProxyAgent, EnvHttpProxyAgent} from 'undici';
    
    // Using a specific ProxyAgent
    const proxyAgent = new ProxyAgent('http://proxy.example.com:8080');
    const response = await ky('https://example.com', {
    	dispatcher: proxyAgent
    }).json();
    
    // Using EnvHttpProxyAgent to read from environment variables
    const api = ky.extend({
    	dispatcher: new EnvHttpProxyAgent()
    });
  7. Set a custom Content-Type header

    main

    While Ky automatically sets the Content-Type based on the request body, you can manually override it using the headers option. This is useful for non-standard APIs (e.g., application/x-amz-json-1.1).

    import ky from 'ky';
    
    const json = await ky.post('https://example.com', {
    	headers: {
    		'content-type': 'application/x-amz-json-1.1'
    	},
    	json: {
    		foo: true
    	},
    }).json();
  8. Paginate API responses with fetch-extras

    main

    You can use fetch-extras to handle pagination by passing ky as the fetchFunction to the paginate utility.

    import ky from 'ky';
    import {paginate} from 'fetch-extras';
    
    const url = 'https://api.github.com/repos/sindresorhus/ky/commits';
    
    for await (const commit of paginate(url, {fetchFunction: ky})) {
    	console.log(commit.sha);
    }
  9. Enable HTTP/2 support in Node.js

    main

    HTTP/2 is not enabled by default in Node.js via Undici. To enable it, you must create a custom dispatcher using undici's Agent and Pool with the allowH2: true option, then pass it to the dispatcher option in Ky.

    import ky from 'ky';
    import {Agent, Pool} from 'undici';
    
    const agent = new Agent({
    	factory(origin, options) {
    		return new Pool(origin, {
    			...options,
    			alowH2: true
    		});
    	}
    });
    
    const response = await ky('https://example.com', {
    	dispatcher: agent
    }).json();
  10. Stop retrying early in `beforeRetry` hook

    main

    If you want to prevent further retries during a retry cycle, you can either:

    1. Throw an error: This stops retrying and propagates the error to the caller.
    2. Return ky.stop: This stops retrying silently; the request will resolve with undefined.
    import ky, {isHTTPError} from 'ky';
    
    const response = await ky('https://example.com', {
    	hooks: {
    		beforeRetry: [
    			({error}) => {
    				if (isHTTPError(error) && error.response.status === 400) {
    					throw error; // Stop retrying, propagate the error
    				}
    			}
    		]
    	}
    });
  11. Consume Server-Sent Events (SSE)

    main

    To consume SSE streams with Ky, it is recommended to use the parse-sse package to iterate over events.

    import ky from 'ky';
    import {parseServerSentEvents} from 'parse-sse';
    
    const response = await ky('https://api.example.com/events');
    
    for await (const event of parseServerSentEvents(response)) {
    	console.log(event.data);
    }