UNPKG

guardz-axios

Version:

Type-safe HTTP client built on top of Axios with runtime validation using guardz. Part of the guardz ecosystem for comprehensive TypeScript type safety.

732 lines (548 loc) 16.9 kB
# Guardz Axios [![GitHub Sponsors](https://img.shields.io/badge/GitHub%20Sponsors-Support%20this%20project-red?style=for-the-badge&logo=github)](https://github.com/sponsors/thiennp) A type-safe HTTP client built on top of Axios with runtime validation using [guardz](https://github.com/thiennp/guardz) 1.11.3+. ## What This Library Does Guardz Axios transforms your HTTP requests into type-safe operations that automatically validate response data at runtime. Instead of manually checking response types and handling errors, you get: - **Automatic runtime validation** of all response data - **Type-safe results** with discriminated unions - **Comprehensive error handling** with detailed feedback - **Multiple API patterns** for different use cases - **Retry logic** with configurable backoff strategies - **Tolerance mode** for graceful degradation ## Installation ```bash npm install guardz-axios guardz axios ``` ## Guardz Ecosystem This library is part of the **Guardz ecosystem** - a comprehensive TypeScript type guard solution designed to bring runtime type safety to your applications: ### 🌟 **Core Components** #### **[guardz](https://www.npmjs.com/package/guardz)** - Core Type Guard Library The foundation of the ecosystem, providing 50+ built-in type guards and utilities: ```bash npm install guardz ``` **Features:** - **50+ built-in type guards** for all JavaScript types - **Composite type guards** for complex validation - **Custom type guard creation** with `isType()` and `isOneOf()` - **Array and object validation** with `isArrayOf()` and `isRecord()` - **Optional and nullable types** with `isOptional()` and `isNullable()` - **Union types** with `isUnion()` and `isOneOf()` - **Intersection types** with `isIntersection()` **Example:** ```typescript import { isType, isString, isNumber, isOptional, isArrayOf } from "guardz"; interface User { id: number; name: string; email?: string; tags: string[]; } const isUser = isType<User>({ id: isNumber, name: isString, email: isOptional(isString), tags: isArrayOf(isString), }); // Runtime validation if (isUser(data)) { // data is guaranteed to be User type console.log(data.name); // Type-safe access } ``` #### **[guardz-generator](https://www.npmjs.com/package/guardz-generator)** - Automatic Type Guard Generation Automatically generate type guards from your TypeScript interfaces and types: ```bash npm install guardz-generator ``` **Features:** - **Automatic generation** from TypeScript interfaces - **CLI tool** for batch processing - **Watch mode** for development - **Custom templates** for specialized guards - **Integration** with build tools and IDEs **Usage:** ```bash # Generate guards from all TypeScript files npx guardz-generator generate "src/**/*.ts" # Watch mode for development npx guardz-generator watch "src/**/*.ts" # Generate guards for specific files npx guardz-generator generate src/types/user.ts src/types/post.ts ``` **Generated Output:** ```typescript // Auto-generated from interface User export const isUser = isType<User>({ id: isNumber, name: isString, email: isOptional(isString), tags: isArrayOf(isString), }); // Auto-generated from interface Post export const isPost = isType<Post>({ id: isNumber, title: isString, content: isString, author: isUser, publishedAt: isOptional(isString), }); ``` #### **[guardz-axios](https://www.npmjs.com/package/guardz-axios)** - Type-Safe HTTP Client This package - a type-safe HTTP client with runtime validation: ```bash npm install guardz-axios ``` **Features:** - **Runtime validation** of all HTTP responses - **Type-safe results** with discriminated unions - **Multiple API patterns** (curried, fluent, configuration-first) - **Retry logic** with exponential backoff - **Tolerance mode** for graceful degradation - **Comprehensive error handling** **Example:** ```typescript import { safeGet } from "guardz-axios"; import { isUser } from "./guards"; // Generated by guardz-generator const result = await safeGet({ guard: isUser })("/users/1"); if (result.status === Status.SUCCESS) { console.log(result.data.name); // Type-safe access } else { console.error(`Error: ${result.message}`); } ``` #### **[guardz-event](https://www.npmjs.com/package/guardz-event)** - Type-Safe Event Handling Type-safe event handling with runtime validation: ```bash npm install guardz-event ``` **Features:** - **Type-safe event emitters** with runtime validation - **Event type guards** for payload validation - **Event listeners** with automatic type inference - **Event middleware** for transformation and filtering - **Event history** and replay capabilities - **Integration** with Node.js EventEmitter and browser events **Example:** ```typescript import { createTypedEventEmitter } from "guardz-event"; import { isType, isString, isNumber } from "guardz"; interface UserCreatedEvent { userId: number; userName: string; } const isUserCreatedEvent = isType<UserCreatedEvent>({ userId: isNumber, userName: isString, }); const emitter = createTypedEventEmitter({ "user:created": isUserCreatedEvent, }); // Type-safe event emission emitter.emit("user:created", { userId: 1, userName: "John" }); // Type-safe event listening emitter.on("user:created", (event) => { console.log(`User created: ${event.userName} (ID: ${event.userId})`); // event is fully typed as UserCreatedEvent }); ``` ### 🔄 **Ecosystem Integration** The guardz ecosystem components work seamlessly together: ```typescript // 1. Define your types interface User { id: number; name: string; email: string; } // 2. Generate type guards automatically // npx guardz-generator generate "src/**/*.ts" // This creates: src/guards/user.guards.ts // 3. Use in HTTP requests import { safeGet } from "guardz-axios"; import { isUser } from "./guards/user.guards"; const result = await safeGet({ guard: isUser })("/users/1"); // 4. Use in event handling import { createTypedEventEmitter } from "guardz-event"; const emitter = createTypedEventEmitter({ "user:updated": isUser, }); emitter.on("user:updated", (user) => { // user is fully typed as User console.log(`User updated: ${user.name}`); }); ``` ### 🚀 **Getting Started with the Ecosystem** 1. **Install the core packages:** ```bash npm install guardz guardz-generator guardz-axios guardz-event ``` 2. **Set up automatic type guard generation:** ```bash # Add to package.json scripts "scripts": { "generate:guards": "guardz-generator generate \"src/**/*.ts\"", "watch:guards": "guardz-generator watch \"src/**/*.ts\"" } ``` 3. **Generate guards from your types:** ```bash npm run generate:guards ``` 4. **Use in your application:** ```typescript import { safeGet } from "guardz-axios"; import { createTypedEventEmitter } from "guardz-event"; import { isUser, isPost } from "./guards"; // Auto-generated // Type-safe HTTP requests const userResult = await safeGet({ guard: isUser })("/users/1"); // Type-safe event handling const emitter = createTypedEventEmitter({ "user:created": isUser, "post:published": isPost, }); ``` ### 📚 **Documentation Links** - **[guardz Documentation](https://github.com/thiennp/guardz)** - Core type guard library - **[guardz-generator Documentation](https://github.com/thiennp/guardz-generator)** - Automatic type guard generation - **[guardz-axios Documentation](https://github.com/thiennp/guardz-axios)** - Type-safe HTTP client (this package) - **[guardz-event Documentation](https://github.com/thiennp/guardz-event)** - Type-safe event handling ### 🎯 **Use Cases** The guardz ecosystem is perfect for: - **API Development** - Validate all incoming/outgoing data - **Event-Driven Applications** - Type-safe event handling - **Microservices** - Ensure data consistency across services - **Frontend Applications** - Validate API responses and user input - **Backend Services** - Validate database queries and external API calls - **Testing** - Generate type-safe test data and mocks ### 🔧 **Development Workflow** 1. **Define your types** in TypeScript interfaces 2. **Generate type guards** automatically with guardz-generator 3. **Use guards** in your HTTP requests with guardz-axios 4. **Handle events** type-safely with guardz-event 5. **Enjoy** full runtime type safety across your application ## Quick Start ```typescript import { safeGet } from "guardz-axios"; import { isType, isString, isNumber } from "guardz"; import { Status } from "guardz-axios"; interface User { id: number; name: string; email: string; } const isUser = isType<User>({ id: isNumber, name: isString, email: isString, }); const result = await safeGet({ guard: isUser })("/users/1"); if (result.status === Status.SUCCESS) { console.log("User:", result.data); // Fully typed as User } else { console.log("Error:", result.code, result.message); } ``` ## How It Works ### Result Type The library uses a **discriminated union** for type-safe results: ```typescript type SafeRequestResult<T> = | { status: Status.SUCCESS; data: T } | { status: Status.ERROR; code: number; message: string }; ``` ### Success Response When the request succeeds and validation passes: ```typescript { status: Status.SUCCESS, data: T // Your validated data } ``` ### Error Response When the request fails or validation fails: ```typescript { status: Status.ERROR, code: number, // HTTP status code or 500 for validation errors message: string // Human-readable error message } ``` ## Error Types and Messages ### 1. Validation Errors (Code: 500) When response data doesn't match the expected type: ```typescript { status: Status.ERROR, code: 500, message: "Response data validation failed: Expected userData.id (\"1\") to be \"number\"" } ``` ### 2. Network Errors (Code: 500) When network requests fail: ```typescript { status: Status.ERROR, code: 500, message: "Network Error" } ``` ### 3. Timeout Errors (Code: 500) When requests timeout: ```typescript { status: Status.ERROR, code: 500, message: "timeout of 5000ms exceeded" } ``` ### 4. HTTP Status Errors When the server returns error status codes: ```typescript { status: Status.ERROR, code: 404, message: "Not Found" } ``` ## Error Handling Examples ### Basic Error Handling ```typescript const result = await safeGet({ guard: isUser })("/users/1"); if (result.status === Status.SUCCESS) { console.log("Success:", result.data); } else { console.error(`Request failed: ${result.message}`); console.error(`Status: ${result.code}`); } ``` ### Tolerance Mode Error Handling ```typescript const result = await safeGet({ guard: isUser, tolerance: true, onError: (error, context) => { console.warn(`Validation warning: ${error}`); console.warn(`Context: ${context.url} (${context.method})`); }, })("/users/1"); if (result.status === Status.SUCCESS) { console.log("Data is valid:", result.data); } else { console.log("Request failed:", result.message); } ``` ### Retry with Error Handling ```typescript const result = await safeRequest({ url: "/users/1", method: "GET", guard: isUser, retry: { attempts: 3, delay: 1000, backoff: "exponential", }, }); if (result.status === Status.SUCCESS) { console.log("Request succeeded:", result.data); } else { console.error(`Request failed: ${result.message}`); } ``` ### Error Handling The library provides detailed error messages and multiple error handling strategies: ### Best Practices for Error Handling 1. **Always check the status first**: ```typescript if (result.status === Status.SUCCESS) { // Handle success } else { // Handle error } ``` 2. **Handle different error types appropriately**: ```typescript if (result.status === Status.ERROR) { switch (result.code) { case 404: // Handle not found break; case 500: // Handle server errors break; } } ``` 3. **Use tolerance mode for graceful degradation**: ```typescript const result = await safeGet({ guard: isUser, tolerance: true, })("/users/1"); if (result.status === Status.SUCCESS) { // Use data confidently } else { // Handle error } ``` 4. **Use detailed error messages**: ```typescript const result = await safeGet({ guard: isUser, onValidationError: (errors) => { errors.forEach((error) => { console.error(`Validation failed: ${error}`); }); }, })("/users/1"); ``` ## API Patterns ### Pattern 1: Curried Functions Simple, functional approach: ```typescript import { safeGet, safePost } from "guardz-axios"; const getUser = safeGet({ guard: isUser }); const createUser = safePost({ guard: isUser }); const result = await getUser("/users/1"); if (result.status === Status.SUCCESS) { console.log("User:", result.data); } ``` ### Pattern 2: Configuration-first Full control over request configuration: ```typescript import { safeRequest } from "guardz-axios"; const result = await safeRequest({ url: "/users/1", method: "GET", guard: isUser, timeout: 5000, retry: { attempts: 3, delay: 1000, backoff: "exponential", }, }); if (result.status === Status.SUCCESS) { console.log("User:", result.data); } ``` ### Pattern 3: Fluent API Builder Chainable, readable API: ```typescript import { safe } from "guardz-axios"; const result = await safe() .get("/users/1") .guard(isUser) .timeout(5000) .retry({ attempts: 3, delay: 1000 }) .execute(); if (result.status === Status.SUCCESS) { console.log("User:", result.data); } ``` ### Pattern 4: Context/Provider Shared configuration across requests: ```typescript import { createSafeApiContext } from "guardz-axios"; const api = createSafeApiContext({ baseURL: "https://api.example.com", timeout: 5000, defaultTolerance: true, }); const result = await api.get("/users/1", { guard: isUser }); if (result.status === Status.SUCCESS) { console.log("User:", result.data); } ``` ## Advanced Features ### Tolerance Mode Handle invalid responses gracefully: ```typescript const result = await safeGet({ guard: isUser, tolerance: true, onError: (error, context) => { console.warn(`Validation warning: ${error}`); }, })("/users/1"); if (result.status === Status.SUCCESS) { console.log("User:", result.data); } else { console.log("Request failed:", result.message); } ``` ### Retry Logic Automatic retry with exponential backoff: ```typescript const result = await safeGet({ guard: isUser, retry: { attempts: 3, delay: 1000, backoff: "exponential", retryOn: (error) => { // Custom retry logic return error.message.includes("network"); }, }, })("/users/1"); if (result.status === Status.SUCCESS) { console.log("User:", result.data); } ``` ### Custom Axios Instance Use your own Axios configuration: ```typescript import axios from "axios"; const customAxios = axios.create({ baseURL: "https://api.example.com", headers: { Authorization: "Bearer token" }, }); const result = await safeGet({ guard: isUser, axiosInstance: customAxios, })("/users/1"); if (result.status === Status.SUCCESS) { console.log("User:", result.data); } ``` ## Type Safety ### Automatic Type Inference ```typescript const result = await safeGet({ guard: isUser })("/users/1"); if (result.status === Status.SUCCESS) { // TypeScript knows this is User console.log(result.data.name); // ✅ Type-safe console.log(result.data.email); // ✅ Type-safe } ``` ### Type Guards ```typescript import { isType, isString, isNumber } from "guardz"; const isUser = isType<User>({ id: isNumber, name: isString, email: isString, }); const result = await safeGet({ guard: isUser })("/users/1"); ``` > **💡 Tip**: Use [guardz-generator](https://www.npmjs.com/package/guardz-generator) to automatically generate type guards from your TypeScript interfaces instead of writing them manually! ## Examples See the [examples](./examples) directory for complete working examples: - [Basic Usage](./examples/basic-usage.ts) - [Advanced Patterns](./examples/advanced-patterns.ts) - [Quick Demo](./examples/quick-demo.ts) - [Error Handling Demo](./examples/error-handling-demo.ts) ## Sponsors Support this project by becoming a sponsor: - [GitHub Sponsors](https://github.com/thiennp) - [Open Collective](https://opencollective.com/guardz-axios) - [Ko-fi](https://ko-fi.com/nguyenphongthien) - [Tidelift](https://tidelift.com/funding/github/npm/guardz-axios) - [Liberapay](https://liberapay.com/~1889616) - [IssueHunt](https://issuehunt.io/r/nguyenphongthien) - [Custom Sponsorship](https://github.com/thiennp/guardz-axios#sponsors) ## License MIT