typed-axios-manager
v1.0.4
Published
Type-safe axios manager with autocomplete and path binding support
Maintainers
Readme
Typed Axios Manager
Un gestor de axios fuertemente tipado con autocompletado inteligente, soporte para rutas dinámicas y parámetros de consulta opcionales.
Comentario del Creador: Por que cree este typed manager? Cuando inicie el camino en la programacion, me incertaron en la mente que hay que hacer las cosas de la manera más eficiente posible. Siempre buscar optimizar el codigo,funciones pero tambien nuestra forma de codear. Hoy con la IA podemos ser muy eficientes, pero no hay que dejar de crear las herramientas que mantengan organizado nuestros proyecto y mejorar nuestra experiencia de desarrollo.
Arranque creando archivos para todos los path. Rapidamente se convirtio en un sin fin de constantes.
Un claro ejemplo que seguramente todos recordaremos:
const PATH_CREATE_USER = "/api/user/"
const PATH_GET_USER = (id: number)=>`/api/user/${id}`
const PATH_UPDATE_USER = (id: number)=> `/api/user/${id}`
...Y mientras buscaba proyectos mas grandes, surgio la necesidad de organizar esto diferente Probe con una carpeta API y archivos x endpoint
api/
├── auth/
│ ├── login.ts
│ ├── register.ts
│ ├── me.ts
├── users/
│ ├── getAll.ts
│ ├── getById.ts
│ ├── create.ts
│ ├── update.ts
│ ├── delete.ts
│ ├── search.ts
├── products/
│ ├── getAll.ts
│ ├── getById.ts
│ ├── search.tsPero no resolvia el problema de las constantes, y los archivos crecian sin parar. Sin contar que cuando empezas, tenes poca organizacion. Cambias objetos, se te rompen request y despues anda a acordarte donde esta y que cambio. Cosas como tener que cambiar el path en MIL lugares, cuando deberia cambiarlo en 1.
Despues conoci React - con sus miles de componentes (los que yo creaba) + Axios Y facilmente empece a crear axios.get("y un path") en un componente, y luego usar en otro, y asi sucesivamente.
Que archivo use? En que componente esta? en un child-component del form? Organizarme es fundamental. Los proyectos grandes requieren que seamos desarrolladores organizados.
En mi experiencia pase por Redux, Zustand, librerias que me gustan mucho por la organizacion Aunque el que tiene que mejorar en organizacion soy yo, me inspire en la forma en la que ellos tienen sus generadores.
Dije: esto, para un axios, estaria barbaro.
Empece a probar, genere varias versiones, y ahora me siento lo suficientemente confiado para mostrar este invento.
Lo tengo funcionando en 2 proyectos. Esta es la primera version que subo porque quiero que sea una dependencia en y no un conjunto de archivos dentro de /helper.
La idea es tener mas organizado los proyectos y la conexion con el backend, en un solo archivo Una unica fuente de la verdad Menos erroes, mas proyectos, mas rapido, mas $$$$
Si a alguien le sirve, encantado de saberlo. Happy Coding!
🚀 Características
- TypeScript First: Tipado fuerte con autocompletado inteligente
- Fluent API: Definición de rutas clara y escalable que permite inferencia correcta de tipos
- Query Params Opcionales: No más objetos vacíos obligatorios
- Path Bindings: Soporte para rutas dinámicas con
{param} - Framework Agnostic: Funciona con React, Vue, Node.js, Next.js, Astro
- Sin Dependencias de Estado: No requiere Redux ni ningún estado específico
- Testing Ready: Tests incluidos con Vitest
- ESM + CommonJS: Compatible con ambos sistemas de módulos
- Hooks de React Opcionales: Usa los hooks si quieres, no es obligatorio
📦 Instalación
npm install typed-axios-manager axios
🔧 Uso Básico
1. Define tus rutas
import { createRouteConfig, get, post, put, del } from 'typed-axios-manager';
type User = { name: string; email: string };
type Product = { name: string; price: number };
type ProductSearchRequest = { name: string; limit?: number; page: number };
const apiRoutes = createRouteConfig({
auth: {
login: post('/auth/login').body<{ email: string; password: string }>().response<{ token: string }>(),
register: post('/auth/register').body<{ email: string; password: string }>(),
me: get('/auth/me').response<User>(),
},
users: {
getAll: get('/users').response<User[]>(),
getById: get('/users/{id}').response<User>(), // Ruta dinámica con inferencia de {id}
create: post('/users').body<{ name: string; email: string }>().response<User>(),
update: put('/users/{id}').body<Partial<User>>(),
delete: del('/users/{id}'),
search: get('/users/search').query<{ q: string; limit?: number }>().response<User[]>(), // Con query params
},
products: {
getAll: get('/products').response<Product[]>(),
getById: get('/products/{id}').response<Product>(),
search: get('/products/search').query<ProductSearchRequest>().response<Product[]>(), // query params opcionales
},
});
type ApiRoutes = typeof apiRoutes;2. Crea el manager
const manager = createAxiosManager<ApiRoutes>({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: {
'X-API-Key': 'your-api-key'
}
});
// Crea las funciones tipadas
const api = manager.createTypedRoutes(apiRoutes);3. Usa las funciones
// Query params realmente opcionales
const users = await api.users.getAll(); // No requiere argumentos
// Con query params cuando los necesites
const products = await api.products.search({
q: 'laptop',
limit: 10,
sort: 'price_asc'
});
// Path bindings funcionan perfectamente
const user = await api.users.getById({ id: 123 });
// Body params bien tipados
const newUser = await api.auth.login({
email: '[email protected]',
password: 'password123'
});🧩 Ejemplos de Query Params
Declarar rutas con tipos de query
import { createRouteConfig, get, post, patch } from 'typed-axios-manager';
const routes = createRouteConfig({
users: {
search: get('/users/search').query<{ q: string; limit?: number }>().response<unknown>(),
getById: get('/users/{id}').query<{ include?: 'profile' | 'roles' }>().response<unknown>(),
create: post('/users').body<{ name: string; email: string }>().query<{ sendWelcomeEmail?: boolean }>().response<unknown>(),
partialUpdate: patch('/users/{id}').body<{ name?: string; email?: string }>().query<{ notify?: boolean }>().response<unknown>(),
},
});Llamadas con y sin query
// GET sin query
await api.users.search();
// GET con query tipada
await api.users.search({ q: 'neo', limit: 10 });
// GET con bindings y query
await api.users.getById({ include: 'profile' }, { id: 1 });
// POST con body, sin query
await api.users.create({ name: 'John', email: '[email protected]' });
// POST con body y query
await api.users.create(
{ name: 'John', email: '[email protected]' },
{ sendWelcomeEmail: true }
);
// PATCH con body y bindings
await api.users.partialUpdate({ name: 'John' }, { id: 1 });
// PATCH con body, query y bindings
await api.users.partialUpdate(
{ name: 'John' },
{ notify: true },
{ id: 1 }
);💡 Evolución y Fluent API
Originalmente, esta librería usaba genéricos directos como get<Response>('/path'). Sin embargo, esto causaba un problema en TypeScript: al especificar un tipo genérico (Response), TypeScript dejaba de inferir automáticamente el literal del path string.
El problema anterior:
// TypeScript infiere Path como 'string' genérico, perdiendo los params {id}
get<Product>('/products/{id}')
// Resultado: api.products.getById() <- No pide argumentosLa solución Fluent API:
// 1. Infiere el path primero ('/products/{id}')
// 2. Añade tipos incrementalmente
get('/products/{id}').response<Product>()
// Resultado: api.products.getById({ id: 1 }) <- Correctamente tipadoEsta nueva sintaxis permite mantener la inferencia precisa de los parámetros de ruta mientras se definen fuertemente los tipos de respuesta, body y query params.
📋 Orden de argumentos y bindings
Los helpers aceptan argumentos en un orden consistente que determina cómo se construye la request de axios:
// GET / DELETE
// Sin bindings: (query?)
await api.users.getAll();
await api.users.search({ q: 'neo', limit: 10 });
// Con bindings: (bindings) o (query, bindings)
await api.users.getById({ id: 1 });
await api.users.getById({ include: 'profile' }, { id: 1 });
// POST / PUT / PATCH
// Sin bindings: (body, query?)
await api.users.create({ name: 'John', email: '[email protected]' });
await api.users.create({ name: 'John', email: '[email protected]' }, { sendWelcomeEmail: true });
// Con bindings: (body, bindings) o (body, query, bindings)
await api.users.update({ name: 'Jane' }, { id: 3 });
await api.users.partialUpdate({ name: 'Neo' }, { notify: true }, { id: 99 });bindingsreemplaza variables de ruta{id}en elurl(versrc/core/AxiosManager.ts:113-121).queryse envía comoaxiosparams(ver GET/DELETE ensrc/core/AxiosManager.ts:75-86).bodyse envía comoaxiosdata(ver POST/PUT/PATCH ensrc/core/AxiosManager.ts:101-106).
Esto permite que las firmas sean limpias y que el path se infiera directamente del argumento de la función, evitando repetirlo en los genéricos.
📘 Uso de TypeScript
Tipar rutas y obtener funciones tipadas
const routes = createRouteConfig({
products: {
// Definir respuesta en la configuración (Recomendado)
getAll: get('/products').response<Product[]>(),
// Definir query params
search: get('/products/search').query<{ q: string; limit?: number }>().response<Product[]>(),
},
});
type Routes = typeof routes;
const manager = createAxiosManager<Routes>({ baseURL: 'https://api.example.com' });
const api = manager.createTypedRoutes(routes);Tipar la respuesta al llamar (Override)
Si no defines el tipo de respuesta en la configuración, o quieres sobrescribirlo:
type Product = { id: number; name: string };
// Si la ruta se definió como: get('/products') -> response es unknown
const products = await api.products.getAll<Product[]>();
// products: Product[]
// También funciona para sobrescribir el tipo definido
const products = await api.products.getAll<CustomProductType[]>();Tipos auxiliares disponibles
import type { QueryParams, PathBindings, ResponseWrapper } from 'typed-axios-manager';
const qp: QueryParams = { q: 'neo', limit: 10 };
const pb: PathBindings = { id: 1 };
const res: ResponseWrapper<{ ok: boolean }> = { code: 'SUCCESS', httpStatus: 200, message: 'OK', data: { ok: true } };🎯 Uso con React (Opcional)
Con Hooks
import { useApiCall, useAuth } from 'typed-axios-manager';
function UserProfile({ userId }: { userId: number }) {
const { execute, loading, error, data: user } = useApiCall(manager);
useEffect(() => {
execute(() => api.users.getById({ id: userId }));
}, [userId, execute]);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
if (!user) return <div>No user found</div>;
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}Con Context Provider
import { createAxiosContext } from 'typed-axios-manager';
const { Provider: AxiosProvider, useApi, useManager } = createAxiosContext<ApiRoutes>();
function App() {
return (
<AxiosProvider manager={manager}>
<YourComponents />
</AxiosProvider>
);
}
function YourComponent() {
const api = useApi();
const manager = useManager();
// Usa api.auth.login(), api.users.getAll(), etc.
}🛠️ Uso con Node.js/Next.js/Astro
// server/api.ts
import { createAxiosManager, createRouteConfig, get, post } from 'typed-axios-manager';
const routes = createRouteConfig({
posts: {
getAll: get('/posts'),
create: post('/posts'),
},
});
const apiManager = createAxiosManager<typeof routes>({
baseURL: process.env.API_URL || 'https://api.example.com',
});
export const api = apiManager.createTypedRoutes(routes);
// pages/api/posts.ts (Next.js)
import { api } from '../server/api';
export default async function handler(req, res) {
try {
const posts = await api.posts.getAll();
res.json(posts);
} catch (error) {
res.status(500).json({ error: error.message });
}
}⚙️ Configuración Avanzada
Headers Dinámicos
// Establecer token de autenticación
manager.setAuthToken('your-jwt-token');
// Agregar headers personalizados
manager.setHeader('X-Custom-Header', 'value');
// Remover token
manager.removeAuthToken();Múltiples Instancias
const publicApi = createAxiosManager({
baseURL: 'https://api.public.com'
});
const privateApi = createAxiosManager({
baseURL: 'https://api.private.com'
});
const publicRoutes = publicApi.createTypedRoutes(publicRouteConfig);
const privateRoutes = privateApi.createTypedRoutes(privateRouteConfig);📚 API Reference
createAxiosManager(config)
Crea una nueva instancia del manager.
Parámetros:
baseURL?: string- URL base para todas las peticionestimeout?: number- Timeout en milisegundos (default: 5000)headers?: Record<string, string>- Headers por defecto
Retorna: AxiosManager<T>
createRouteConfig(config)
Crea una configuración de rutas tipadas.
Retorna: T (tipado según tu configuración)
Helpers de rutas
get(path)- GET requestpost(path)- POST requestput(path)- PUT requestdel(path)- DELETE requestpatch(path)- PATCH request
Manager Methods
createTypedRoutes(config)- Crea las funciones tipadassetHeader(key, value)- Establece un headersetAuthToken(token)- Establece token de authremoveAuthToken()- Remueve el tokengetInstance()- Obtiene la instancia de axios
React Hooks (opcionales)
useApiCall(manager)- Hook para llamadas con loading/error statesuseAuth(manager)- Hook para autenticacióncreateAxiosContext()- Crea context provider
🤝 Contribuir
¡Las contribuciones son bienvenidas! Por favor:
- Fork el proyecto
- Crea una rama para tu feature (
git checkout -b feature/AmazingFeature) - Commit tus cambios (
git commit -m 'Add some AmazingFeature') - Push a la rama (
git push origin feature/AmazingFeature) - Abre un Pull Request
📝 Licencia
Este proyecto está bajo la Licencia MIT.
