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
Markdown
# Guardz Axios
[](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