npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

httpx-decorators

v3.0.0

Published

Framework-agnostic HTTP client library with decorators, Axios, and Zod validation

Downloads

17

Readme


🚀 Características Principales

| Característica | Descripción | |:--------------:|:-----------| | 🎯 Decoradores Unificados | Configuración completa en un solo objeto | | 📋 @Request() | Validación automática del cuerpo de peticiones | | 📤 @Response() | Captura de respuestas validadas/mapeadas | | 🔒 Tipado Fuerte | TypeScript con inferencia automática de tipos Zod | | ✅ Validación Automática | Request y response validation con Zod | | 🌐 Framework Agnóstico | Compatible con Angular, React, Node.js, etc. | | 🪝 Sistema de Hooks | Middlewares personalizables (onRequest, onResponse, onError) | | ⚡ Basado en Axios | Cliente HTTP robusto y confiable | | 🛡️ Manejo de Errores | Sistema completo con errores personalizados | | 📦 Extensible | Arquitectura modular y fácil de extender |


📦 Instalación

npm install httpx-decorators axios zod reflect-metadata

🛠️ Configuración

Configura tu tsconfig.json:

{
  "compilerOptions": {
    "experimentalDecorators": true,
    "emitDecoratorMetadata": true,
    "target": "ES2020",
    "module": "commonjs"
  }
}

Importa reflect-metadata al inicio de tu aplicación:

import 'reflect-metadata';

🎯 Uso Básico

1. Definir Schemas con Zod

import { z } from 'zod';

// Schema para request
const CreateUserSchema = z.object({
    email: z.string().email(),
    password: z.string().min(6),
    name: z.string().min(2)
});

// Schema para response
const UserResponseSchema = z.object({
    id: z.string(),
    email: z.string(),
    name: z.string(),
    token: z.string()
});

// Clase de dominio
class User {
    constructor(
        public readonly id: string,
        public readonly email: string,
        public readonly name: string,
        public readonly token: string
    ) {}

    static fromApiResponse(data: z.infer<typeof UserResponseSchema>): User {
        return new User(data.id, data.email, data.name, data.token);
    }
}

2. Crear Cliente API

import {
    BaseHttpClient,
    GET, POST, PUT, DELETE,
    Request, Response, Query, Headers, Params,
    createMapper
} from 'httx-decorators';

class ApiClient extends BaseHttpClient {
    constructor() {
        super({
            baseURL: 'https://api.example.com',
            timeout: 10000,
            validateRequest: true,
            validateResponse: true
        });
    }

    /**
     * POST con @Request() para validar el cuerpo de la petición
     */
    @POST({
        url: '/users',
        requestSchema: CreateUserSchema,
        responseSchema: UserResponseSchema,
        headers: { Authorization: true },
        mapper: createMapper((response: z.infer<typeof UserResponseSchema>) =>
            User.fromApiResponse(response)
        )
    })
    async createUser(
        @Request() userData: z.infer<typeof CreateUserSchema>,
        @Headers() headers: { Authorization: string },
        @Response() user: User
    ): Promise<User> {
        console.log('Creating user:', userData.name);
        return user;
    }

    /**
     * GET sin @Request() (típico para métodos GET)
     */
    @GET({
        url: '/users/:id',
        responseSchema: UserResponseSchema,
        params: { id: true },
        headers: { Authorization: true },
        mapper: createMapper((response: z.infer<typeof UserResponseSchema>) =>
            User.fromApiResponse(response)
        )
    })
    async getUserById(
        @Params() params: { id: string },
        @Headers() headers: { Authorization: string },
        @Response() user: User
    ): Promise<User> {
        return user;
    }

    /**
     * PUT con @Request() para validar datos de actualización
     */
    @PUT({
        url: '/users/:id',
        requestSchema: UpdateUserSchema,
        responseSchema: UserResponseSchema,
        params: { id: true },
        headers: { Authorization: true }
    })
    async updateUser(
        @Request() updateData: z.infer<typeof UpdateUserSchema>,
        @Params() params: { id: string },
        @Headers() headers: { Authorization: string },
        @Response() user: User
    ): Promise<User> {
        return user;
    }
}

3. Usar el Cliente

const api = new ApiClient();

try {
    // Crear usuario con validación automática del @Request()
    const newUser = await api.createUser(
        {
            email: '[email protected]',
            password: 'password123',
            name: 'John Doe'
        },
        { Authorization: 'Bearer token' },
        undefined as any // @Response() se inyecta automáticamente
    );

    console.log('User created:', newUser.name);

    // Obtener usuario por ID
    const user = await api.getUserById(
        { id: newUser.id },
        { Authorization: 'Bearer token' },
        undefined as any
    );

    console.log('User retrieved:', user.name);

} catch (error) {
    console.error('Error:', error);
}

🎛️ Decoradores Disponibles


🟢 Decoradores de Método HTTP

Todos los decoradores HTTP reciben un objeto de configuración unificado:

interface HttpDecoratorConfig {
    url: string; // URL del endpoint
    requestSchema?: ZodSchema; // Schema para validar request
    responseSchema?: ZodSchema; // Schema para validar response
    errorType?: ErrorClass; // Clase de error personalizada
    headers?: boolean | object; // Configuración de headers
    query?: boolean | object; // Configuración de query params
    params?: boolean | object; // Configuración de URL params
    timeout?: number; // Timeout específico
    validateRequest?: boolean; // Habilitar validación de request
    validateResponse?: boolean; // Habilitar validación de response
    mapper?: (response: any) => any; // Función de transformación
}

@GET(config)

// Para peticiones GET. Normalmente no requiere @Request().
@GET({
    url: '/users/:id',
    responseSchema: UserSchema,
    params: { id: true }
})
async getUser(
    @Params() params: { id: string },
    @Response() user: User
): Promise<User> {
    return user;
}

@POST(config)

// Para peticiones POST. Usar con @Request() para validar el cuerpo.
@POST({
    url: '/users',
    requestSchema: CreateUserSchema,
    responseSchema: UserSchema
})
async createUser(
    @Request() userData: CreateUserRequest,
    @Response() user: User
): Promise<User> {
    return user;
}

@PUT(config), @PATCH(config), @DELETE(config)

// Similar a POST, usar con @Request() cuando sea necesario validar el cuerpo.
@PUT({
    url: '/users/:id',
    requestSchema: UpdateUserSchema,
    responseSchema: UserSchema,
    params: { id: true }
})
async updateUser(
    @Request() updateData: UpdateUserRequest,
    @Params() params: { id: string },
    @Response() user: User
): Promise<User> {
    return user;
}

🟣 Decoradores de Parámetros


@Request(schema?)

⚠️ Recomendación importante: Usar principalmente en métodos POST, PUT, PATCH.

@POST({
    url: '/users',
    requestSchema: CreateUserSchema
})
async createUser(
    @Request() userData: z.infer<typeof CreateUserSchema>
): Promise<User> {
    // userData ya está validado con CreateUserSchema
    return userData;
}

@Response(schema?)

🏁 Debe colocarse al final del método para asegurar que toda la validación se complete.

@GET({
    url: '/users/:id',
    responseSchema: UserSchema,
    mapper: (data) => new User(data)
})
async getUser(
    @Params() params: { id: string },
    @Response() user: User
): Promise<User> {
    // user ya está validado y mapeado
    return user;
}

@Query(key?, schema?)

// Para inyectar query parameters.
@GET({
    url: '/users',
    query: true
})
async getUsers(
    @Query() query: { page?: number; limit?: number }
): Promise<User[]> {
    // query contiene todos los query parameters
}

@Headers(key?, schema?)

// Para inyectar headers.
@POST({
    url: '/users',
    headers: { Authorization: true }
})
async createUser(
    @Headers() headers: { Authorization: string }
): Promise<User> {
    // headers contiene los headers configurados
}

@Params(key?, schema?)

// Para inyectar parámetros de URL.
@GET({
    url: '/users/:id',
    params: { id: true }
})
async getUser(
    @Params() params: { id: string }
): Promise<User> {
    // params contiene los parámetros de URL
}

🔄 Mappers

Los mappers transforman automáticamente las respuestas validadas a objetos de dominio.

@GET({
    url: '/users/:id',
    responseSchema: UserResponseSchema,
    mapper: createMapper((response: z.infer<typeof UserResponseSchema>) =>
        User.fromApiResponse(response)
    )
})
async getUser(
    @Response() user: User
): Promise<User> {
    // user ya es una instancia de la clase User
    return user;
}

🛡️ Validación y Manejo de Errores

✅ Validación Automática

// El @Request() valida automáticamente con el requestSchema
@POST({
    url: '/users',
    requestSchema: CreateUserSchema // Validación automática
})
async createUser(
    @Request() userData: z.infer<typeof CreateUserSchema>
): Promise<User> {
    // Si userData no cumple el schema, se lanza ValidationError automáticamente
    return userData;
}

🚨 Manejo de Errores

import { ErrorHandler, ValidationError, NetworkError } from 'httx-decorators';

try {
    const result = await api.createUser(invalidData);
} catch (error) {
    if (ErrorHandler.isValidationError(error)) {
        console.error('Validation errors:', ErrorHandler.getValidationErrors(error));
    } else if (ErrorHandler.isNetworkError(error)) {
        console.error('Network error:', ErrorHandler.getNetworkStatus(error));
    } else {
        console.error('Unknown error:', error);
    }
}

🪝 Sistema de Hooks

const api = new ApiClient();

// Hook de request
api.addRequestHook(async (context) => {
    console.log(`Making request to ${context.url}`);
    return context;
});

// Hook de response
api.addResponseHook(async (response, context) => {
    console.log(`Received response from ${context.url}`);
    return response;
});

// Hook de error
api.addErrorHook(async (error, context) => {
    console.error(`Error in ${context.url}:`, error.message);
    return error;
});

📝 Buenas Prácticas


1️⃣ Uso de @Request()

  • POST, PUT, PATCH: Usar @Request() para validar el cuerpo de la petición
  • GET, DELETE: Normalmente no requieren @Request()

2️⃣ Posición de @Response()

  • Siempre al final: Colocar @Response() como último parámetro del método

3️⃣ Validación Request/Response

  • requestSchema: Define el schema para validar @Request()
  • responseSchema: Define el schema para validar @Response()
  • mapper: Transforma la respuesta validada a objetos de dominio

4️⃣ Configuración de Decoradores

// ✅ Buena práctica
@POST({
    url: '/users',
    requestSchema: CreateUserSchema, // Valida @Request()
    responseSchema: UserResponseSchema, // Valida @Response()
    headers: { Authorization: true }, // Habilita headers específicos
    errorType: ValidationError // Error personalizado
})
async createUser(
    @Request() userData: CreateUserRequest,
    @Headers() headers: { Authorization: string },
    @Response() user: User // Al final
): Promise<User> {
    return user;
}

🏗️ Arquitectura

src/
├── types/       # Interfaces y tipos TypeScript con JSDoc
├── decorators/  # Decoradores HTTP con @Request() y JSDoc completo
├── metadata/    # Sistema de metadatos con validación
├── client/      # Cliente HTTP con soporte para @Request()
├── errors/      # Manejo de errores
└── examples/    # Ejemplos ejecutables completos

🚀 Ejemplo Ejecutable

Ejecuta el ejemplo completo:

npm run dev

Este comando ejecuta src/examples/basic-usage.ts que demuestra:

  • Uso de @Request() para validar peticiones POST/PUT
  • Uso de @Response() para capturar respuestas
  • Validación automática con Zod
  • Manejo de errores
  • Mappers para transformar respuestas

🤝 Contribuir

  1. Fork el proyecto
  2. Crea una rama para tu feature (git checkout -b feature/amazing-feature)
  3. Commit tus cambios (git commit -m 'Add amazing feature')
  4. Push a la rama (git push origin feature/amazing-feature)
  5. Abre un Pull Request

📄 Licencia

MIT License