Send JSON and read responses
Use ky to put a JSON value in a request and read the response directly as JSON. Choose a TypeScript type when the response shape is trusted, or pass a Standard Schema when the response must be checked at runtime.
Send a JSON request
Pass the value to the json option instead of serializing it into body. Ky serializes the value and sets Content-Type: application/json unless you provide that header yourself. The request shortcut returns a response promise, so you can call json() without first awaiting a raw response.
tsimport ky from 'ky';
interface NewUser {
name: string;
email: string;
}
interface User extends NewUser {
id: string;
}
const newUser: NewUser = {
name: 'Ada Lovelace',
email: 'ada@example.com',
};
const createUser = async (): Promise<User> => ky
.post<User>('https://api.example.com/users', {json: newUser})
.json();
const showCreatedUser = async (): Promise<void> => {
const user = await createUser();
console.log(user.id, user.name);
};
void showCreatedUser();
The server receives a JSON object with name and email. The response is parsed as JSON and user has the compile-time type User; the type parameter does not validate the server's data at runtime.
Read a typed JSON response
.json() defaults to unknown. Give the request or the body method a type parameter when you want a typed result:
tsimport ky from 'ky';
interface User {
id: string;
name: string;
}
const showTypedUsers = async (): Promise<void> => {
const fromRequestType = await ky<User>('https://api.example.com/users/1').json();
const fromBodyMethodType = await ky('https://api.example.com/users/1').json<User>();
console.log(fromRequestType.name, fromBodyMethodType.id);
};
void showTypedUsers();
Both calls produce a parsed JSON object typed as User. The body method is a direct shortcut; it also sets an appropriate Accept header for JSON. A non-2xx response rejects instead of producing a successful value.
Validate the response with a schema
Install a Standard Schema-compatible validator such as Zod alongside Ky:
bashnpm install ky zod
Pass the schema to .json(schema). Ky returns the validator's inferred output only after validation succeeds. A rejected schema produces SchemaValidationError, not a Ky HTTP lifecycle error.
tsimport ky, {SchemaValidationError} from 'ky';
import {z} from 'zod';
const userSchema = z.object({
id: z.string(),
name: z.string(),
});
const createUser = async (): Promise<void> => {
try {
const user = await ky.post('https://api.example.com/users', {
json: {name: 'Ada Lovelace'},
}).json(userSchema);
console.log(user.id, user.name);
} catch (error) {
if (error instanceof SchemaValidationError) {
console.error('The response shape is invalid', error.issues);
return;
}
throw error;
}
};
const run = async (): Promise<void> => createUser();
void run();
This combines a JSON request with runtime validation of the JSON response: the request sends name, and the call returns a value only when the response also contains a string id and name.
Handle HTTP failures separately
For non-2xx responses and Ky's HTTPError and isHTTPError handling, see Requests and responses.
tsimport ky, {isHTTPError} from 'ky';
const loadUser = async (): Promise<void> => {
try {
const user = await ky('https://api.example.com/users/1').json<{name: string}>();
console.log(user.name);
} catch (error) {
if (isHTTPError(error)) {
console.error('Request failed', error.response.status, error.data);
return;
}
throw error;
}
};
const run = async (): Promise<void> => loadUser();
void run();
error.response remains available for status and headers. If no response arrives, the failure is not an HTTPError; handle network failures separately when your application needs that distinction.
Options that matter here
| Option | Type | Default | What it does |
|---|---|---|---|
json | any value accepted by JSON.stringify() | not set | Serializes the value into the request body and sets Content-Type: application/json unless your headers override it. |
headers | Fetch headers | not set | Supplies request headers; an explicit Content-Type takes precedence over Ky's JSON header. |
throwHttpErrors | boolean | enabled | Controls whether non-2xx responses reject as HTTPError. |
parseJson | function | JSON.parse | Replaces JSON parsing for response bodies, including handling an empty body. |
Pitfalls
- Do not use a TypeScript type parameter as a runtime check. Use
.json(schema)when the response must be validated. .json()cannot parse an empty body by default. ConfigureparseJsonwhen an endpoint legitimately returns an empty response.- Read failed-response content from
HTTPError.data, not fromHTTPError.response.json(). - Keep the request container's URL real in application code. The URLs above are examples; replace them with your API's endpoint.
Was this page helpful?