← Volver al listado de tecnologías
Ky: Cliente HTTP elegante para JavaScript
Ky: Cliente HTTP Elegante
Ky es un cliente HTTP pequeño y elegante basado en la Fetch API. Ofrece una API más simple que fetch nativo, con atajos de métodos, manejo de errores para códigos no-2xx, reintentos automáticos y más.
Instalación
npm install ky
# o
bun add ky
Uso Básico
import ky from 'ky';
// GET simple
const data = await ky.get('https://api.example.com/users').json();
// POST con JSON
const user = await ky.post('https://api.example.com/users', {
json: { name: 'Juan', email: '[email protected]' }
}).json();
// PUT y DELETE
await ky.put('https://api.example.com/users/1', { json: { name: 'Juan Updated' } });
await ky.delete('https://api.example.com/users/1');
Crear Instancia con Configuración
import ky from 'ky';
const api = ky.create({
prefixUrl: 'https://api.example.com/v1',
timeout: 30000,
headers: {
'Authorization': 'Bearer token123'
}
});
// Usa la instancia configurada
const users = await api.get('users').json();
Reintentos Automáticos
const data = await ky.get('https://api.example.com/data', {
retry: {
limit: 3,
methods: ['get', 'post'],
statusCodes: [408, 429, 500, 502, 503, 504],
backoffLimit: 10000
}
}).json();
// Reintentar en timeout
const result = await ky.get('https://slow-api.example.com', {
timeout: 5000,
retry: {
limit: 3,
retryOnTimeout: true
}
}).json();
Hooks
Ky permite interceptar requests en diferentes puntos:
const api = ky.create({
hooks: {
beforeRequest: [
(request) => {
request.headers.set('X-Custom-Header', 'value');
}
],
beforeRetry: [
async ({ request, error, retryCount }) => {
console.log(`Reintento ${retryCount}`);
const newToken = await refreshToken();
request.headers.set('Authorization', `Bearer ${newToken}`);
}
],
beforeError: [
async (error) => {
if (error.response) {
const body = await error.response.json();
error.message = body.message || error.message;
}
return error;
}
]
}
});
Manejo de Errores
import ky, { HTTPError, TimeoutError } from 'ky';
try {
const data = await ky.get('https://api.example.com/data').json();
} catch (error) {
if (error instanceof HTTPError) {
const errorBody = await error.response.json();
console.error(`Error ${error.response.status}:`, errorBody.message);
} else if (error instanceof TimeoutError) {
console.error('La petición excedió el tiempo límite');
}
}
Timeout
// Timeout global
const api = ky.create({ timeout: 10000 });
// Timeout por petición
const data = await ky.get('https://api.example.com', {
timeout: 5000
}).json();
// Sin timeout (útil para uploads)
await ky.post('https://api.example.com/upload', {
body: formData,
timeout: false
});
Query Parameters
const data = await ky.get('https://api.example.com/search', {
searchParams: {
query: 'javascript',
page: 1,
limit: 20
}
}).json();
// URL: https://api.example.com/search?query=javascript&page=1&limit=20
Subida de Archivos
const formData = new FormData();
formData.append('file', file);
formData.append('name', 'mi-archivo');
const result = await ky.post('https://api.example.com/upload', {
body: formData,
timeout: false
}).json();
Ejemplo: Cliente API Completo
import ky, { HTTPError } from 'ky';
class APIClient {
private api: typeof ky;
private token: string | null = null;
constructor(baseURL: string) {
this.api = ky.create({
prefixUrl: baseURL,
timeout: 30000,
retry: { limit: 3 },
hooks: {
beforeRequest: [
(request) => {
if (this.token) {
request.headers.set('Authorization', `Bearer ${this.token}`);
}
}
]
}
});
}
setToken(token: string) {
this.token = token;
}
async getUsers() {
return this.api.get('users').json<User[]>();
}
async createUser(data: CreateUserDTO) {
return this.api.post('users', { json: data }).json<User>();
}
}
// Uso
const client = new APIClient('https://api.example.com/v1');
client.setToken('mi-token');
const users = await client.getUsers();
Ky vs Fetch vs Axios
| Característica | Fetch | Ky | Axios |
|---|---|---|---|
| Tamaño | Nativo | ~4KB | ~13KB |
| Reintentos | Manual | Incluido | Plugin |
| Timeout | Manual | Incluido | Incluido |
| Hooks | No | Sí | Interceptores |
| JSON automático | No | Sí | Sí |