CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-ky

Tiny and elegant HTTP client based on the Fetch API

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

responses.mddocs/

Response Handling

Enhanced response promises with body method shortcuts and automatic error handling for non-2xx status codes. Ky extends the standard Response object with convenient methods for accessing response data.

Capabilities

Response Promise Interface

The ResponsePromise extends the standard Promise with body method shortcuts that automatically set appropriate Accept headers.

interface ResponsePromise<T = unknown> extends Promise<KyResponse<T>> {
  /** Get response body as ArrayBuffer */
  arrayBuffer(): Promise<ArrayBuffer>;
  /** Get response body as Blob */
  blob(): Promise<Blob>;
  /** Get response body as FormData */
  formData(): Promise<FormData>;
  /** Get response body as raw bytes (when supported by runtime) */
  bytes(): Promise<Uint8Array>;
  /** Get response body as JSON with type safety */
  json<J = T>(): Promise<J>;
  /** Get response body as text string */
  text(): Promise<string>;
}

Usage Examples:

import ky from "ky";

// JSON response with automatic type inference
interface User {
  id: number;
  name: string;
  email: string;
}

const user = await ky.get("https://api.example.com/user/123").json<User>();
console.log(user.name); // TypeScript knows this is a string

// Text response
const csvData = await ky.get("https://api.example.com/data.csv").text();
console.log(csvData);

// Binary data as ArrayBuffer
const fileBuffer = await ky.get("https://api.example.com/file.pdf").arrayBuffer();
const uint8Array = new Uint8Array(fileBuffer);

// Blob for file handling
const imageBlob = await ky.get("https://api.example.com/image.jpg").blob();
const imageUrl = URL.createObjectURL(imageBlob);

// FormData response
const formResponse = await ky.get("https://api.example.com/form-data").formData();
console.log(formResponse.get("field-name"));

// Raw bytes (when supported)
if (typeof Response.prototype.bytes === "function") {
  const rawBytes = await ky.get("https://api.example.com/binary").bytes();
  console.log(rawBytes instanceof Uint8Array); // true
}

Enhanced Response Object

Ky responses extend the standard Response with enhanced JSON methods.

interface KyResponse<T = unknown> extends Response {
  /** Enhanced JSON method with type safety */
  json<J = T>(): Promise<J>;
}

Usage Examples:

import ky from "ky";

// Access full response object
const response = await ky.get("https://api.example.com/data");

// Check response properties
console.log("Status:", response.status);
console.log("Status Text:", response.statusText);
console.log("Headers:", Object.fromEntries(response.headers));
console.log("OK:", response.ok);

// Process response based on content type
const contentType = response.headers.get("content-type");

if (contentType?.includes("application/json")) {
  const data = await response.json();
  console.log("JSON data:", data);
} else if (contentType?.includes("text/")) {
  const text = await response.text();
  console.log("Text data:", text);
} else {
  const buffer = await response.arrayBuffer();
  console.log("Binary data:", buffer.byteLength, "bytes");
}

Automatic Header Handling

Response body methods automatically set appropriate Accept headers for content negotiation.

Usage Examples:

import ky from "ky";

// Automatically sets Accept: application/json
const jsonData = await ky.get("https://api.example.com/data").json();

// Automatically sets Accept: text/*  
const textData = await ky.get("https://api.example.com/text").text();

// Automatically sets Accept: multipart/form-data
const formData = await ky.get("https://api.example.com/form").formData();

// Automatically sets Accept: */*
const binaryData = await ky.get("https://api.example.com/binary").arrayBuffer();
const blobData = await ky.get("https://api.example.com/file").blob();

// Manual Accept header override
const customData = await ky.get("https://api.example.com/custom", {
  headers: { "Accept": "application/vnd.api+json" }
}).json();

JSON Response Special Cases

Ky handles JSON responses with special behaviors for edge cases.

Usage Examples:

import ky from "ky";

// Empty response (status 204) returns empty string
const noContent = await ky.delete("https://api.example.com/resource/123").json();
console.log(noContent); // ""

// Empty body returns empty string instead of throwing parse error
const emptyResponse = await ky.get("https://api.example.com/empty").json();
console.log(emptyResponse); // ""

// Custom JSON parsing with error handling
const safeClient = ky.create({
  parseJson: (text) => {
    if (!text.trim()) {
      return null;
    }
    try {
      return JSON.parse(text);
    } catch (error) {
      console.warn("JSON parse error:", error);
      return { error: "Invalid JSON", originalText: text };
    }
  }
});

const safeData = await safeClient.get("https://api.example.com/maybe-json").json();

Response with TypeScript Generics

Leverage TypeScript generics for type-safe response handling.

Usage Examples:

import ky from "ky";

// Generic at call site
interface ApiResponse<T> {
  data: T;
  status: string;
  timestamp: string;
}

interface Product {
  id: number;
  name: string;
  price: number;
}

// Type the entire response
const productResponse = await ky.get("https://api.example.com/products/123")
  .json<ApiResponse<Product>>();

console.log(productResponse.data.name); // TypeScript knows this is string
console.log(productResponse.data.price); // TypeScript knows this is number

// Generic at instance level
const typedClient = ky.create({ prefixUrl: "https://api.example.com" });

const users = await typedClient.get("users").json<User[]>();
const user = await typedClient.get("users/123").json<User>();
const stats = await typedClient.get("stats").json<{ count: number; average: number }>();

// Conditional response types
type ApiResult<T> = {
  success: true;
  data: T;
} | {
  success: false;
  error: string;
};

const result = await ky.post("https://api.example.com/process", {
  json: { input: "data" }
}).json<ApiResult<{ output: string }>>();

if (result.success) {
  console.log(result.data.output); // TypeScript knows this exists
} else {
  console.error(result.error); // TypeScript knows this exists
}

Response Streaming

Handle streaming responses with progress tracking.

Usage Examples:

import ky from "ky";

// Stream large file with progress
const downloadLargeFile = async () => {
  const response = await ky.get("https://api.example.com/large-file.zip", {
    onDownloadProgress: (progress, chunk) => {
      console.log(`Downloaded: ${Math.round(progress.percent * 100)}%`);
      console.log(`Chunk size: ${chunk.length} bytes`);
    }
  });
  
  return response.arrayBuffer();
};

// Process streaming JSON responses
const processStreamingData = async () => {
  const response = await ky.get("https://api.example.com/streaming-data");
  
  const reader = response.body?.getReader();
  const decoder = new TextDecoder();
  
  if (reader) {
    while (true) {
      const { done, value } = await reader.read();
      if (done) break;
      
      const chunk = decoder.decode(value, { stream: true });
      console.log("Received chunk:", chunk);
    }
  }
};

Types

interface ResponsePromise<T = unknown> extends Promise<KyResponse<T>> {
  arrayBuffer(): Promise<ArrayBuffer>;
  blob(): Promise<Blob>;
  formData(): Promise<FormData>;
  bytes(): Promise<Uint8Array>;
  json<J = T>(): Promise<J>;
  text(): Promise<string>;
}

interface KyResponse<T = unknown> extends Response {
  json<J = T>(): Promise<J>;
}

interface KyRequest<T = unknown> extends Request {
  json<J = T>(): Promise<J>;
}

docs

configuration.md

errors.md

hooks.md

http-methods.md

index.md

instances.md

responses.md

retry.md

tile.json