decaf's Http Module
A TypeScript library for seamless REST API interactions. This module provides a flexible and type-safe way to communicate with HTTP-based services using the repository pattern. It includes adapters for different HTTP clients (with Axios implementation provided), repository and service classes for CRUD operations, and comprehensive type definitions to ensure type safety throughout your API interactions.
Documentation available here
Description
The @decaf-ts/for-http
library provides a robust and type-safe solution for interacting with REST APIs in TypeScript applications. Built on top of the core Decaf framework, it implements the repository pattern to offer a clean and consistent interface for HTTP operations.
Architecture
The library is structured around several key components:
-
HTTP Adapter: The
HttpAdapter
class serves as the foundation, providing an abstract interface for HTTP operations. It handles URL construction, error parsing, and implements the adapter pattern to work with different HTTP clients. -
Axios Implementation: The library includes a concrete implementation of the HTTP adapter using Axios (
AxiosHttpAdapter
), demonstrating how to integrate with popular HTTP clients. -
Repository Pattern: The
RestRepository
class extends the core Repository class to provide a high-level interface for CRUD operations on REST resources. It works with model classes and handles the mapping between your domain models and API endpoints. -
Service Layer: The
RestService
class offers both individual and bulk CRUD operations, with support for the observer pattern to notify subscribers of changes. -
Type Definitions: Comprehensive type definitions ensure type safety throughout your API interactions, with interfaces like
HttpFlags
andHttpConfig
providing configuration options.
Key Features
- Type Safety: Leverages TypeScript's type system to ensure API interactions are type-safe
- Repository Pattern: Implements the repository pattern for clean separation of concerns
- CRUD Operations: Provides standard create, read, update, and delete operations
- Bulk Operations: Supports bulk operations for efficient handling of multiple resources
- Extensibility: Designed to be extended with different HTTP client implementations
- Error Handling: Includes robust error handling and parsing
- Observer Pattern: Implements the observer pattern for reactive programming
This library is ideal for applications that need to interact with REST APIs in a structured, type-safe manner, particularly those already using the Decaf framework.
How to Use
Basic Usage
Setting Up an HTTP Adapter with Axios
import axios from 'axios';
import { AxiosHttpAdapter } from '@decaf-ts/for-http';
// Create an HTTP configuration
const config = {
protocol: 'https',
host: 'api.example.com'
};
// Create an Axios HTTP adapter
const httpAdapter = new AxiosHttpAdapter(axios.create(), config);
Creating a Model Class
import { Model } from '@decaf-ts/decorator-validation';
class User extends Model {
id: string;
name: string;
email: string;
constructor(data?: Partial<User>) {
super();
Object.assign(this, data);
}
}
Using RestRepository for CRUD Operations
import { RestRepository } from '@decaf-ts/for-http';
// Create a repository for the User model
const userRepository = new RestRepository(httpAdapter, User);
// Create a new user
const newUser = new User({
name: 'John Doe',
email: 'john@example.com'
});
const createdUser = await userRepository.create(newUser);
// Read a user by ID
const user = await userRepository.findById('123');
// Update a user
user.name = 'Jane Doe';
await userRepository.update(user);
// Delete a user
await userRepository.delete('123');
Using RestService for Advanced Operations
import { RestService } from '@decaf-ts/for-http';
// Create a service for the User model
const userService = new RestService(httpAdapter, User);
// Create a new user
const newUser = new User({
name: 'John Doe',
email: 'john@example.com'
});
const createdUser = await userService.create(newUser);
// Read a user by ID
const user = await userService.read('123');
// Update a user
user.name = 'Jane Doe';
await userService.update(user);
// Delete a user
await userService.delete('123');
Bulk Operations with RestService
import { RestService } from '@decaf-ts/for-http';
// Create a service for the User model
const userService = new RestService(httpAdapter, User);
// Create multiple users
const users = [
new User({ name: 'John Doe', email: 'john@example.com' }),
new User({ name: 'Jane Doe', email: 'jane@example.com' })
];
const createdUsers = await userService.createAll(users);
// Read multiple users by ID
const userIds = ['123', '456'];
const fetchedUsers = await userService.readAll(userIds);
// Update multiple users
const usersToUpdate = [
new User({ id: '123', name: 'John Smith' }),
new User({ id: '456', name: 'Jane Smith' })
];
const updatedUsers = await userService.updateAll(usersToUpdate);
// Delete multiple users
await userService.deleteAll(['123', '456']);
Using the Observer Pattern
import { RestService } from '@decaf-ts/for-http';
import { Observer } from '@decaf-ts/core';
// Create a service for the User model
const userService = new RestService(httpAdapter, User);
// Create an observer
const userObserver: Observer<User> = {
update: (user) => {
console.log('User updated:', user);
}
};
// Register the observer
userService.observe(userObserver);
// When operations are performed, observers will be notified
await userService.create(new User({ name: 'John Doe' }));
// Unregister the observer when done
userService.unObserve(userObserver);
Custom HTTP Adapter Implementation
import { HttpAdapter } from '@decaf-ts/for-http';
import { HttpConfig, HttpFlags } from '@decaf-ts/for-http';
import { Context } from '@decaf-ts/db-decorators';
import SomeHttpClient from 'some-http-client';
// Create a custom HTTP adapter for a different HTTP client
class CustomHttpAdapter extends HttpAdapter<
SomeHttpClient,
any,
HttpFlags,
Context<HttpFlags>
> {
constructor(native: SomeHttpClient, config: HttpConfig, alias?: string) {
super(native, config, 'custom', alias);
}
async request<V>(details: any): Promise<V> {
return this.native.sendRequest(details);
}
async create(tableName: string, id: string | number, model: Record<string, any>): Promise<Record<string, any>> {
try {
const url = this.url(tableName);
return this.native.post(url, model);
} catch (e: any) {
throw this.parseError(e);
}
}
async read(tableName: string, id: string | number | bigint): Promise<Record<string, any>> {
try {
const url = this.url(tableName, { id: id as string | number });
return this.native.get(url);
} catch (e: any) {
throw this.parseError(e);
}
}
async update(tableName: string, id: string | number, model: Record<string, any>): Promise<Record<string, any>> {
try {
const url = this.url(tableName);
return this.native.put(url, model);
} catch (e: any) {
throw this.parseError(e);
}
}
async delete(tableName: string, id: string | number | bigint): Promise<Record<string, any>> {
try {
const url = this.url(tableName, { id: id as string | number });
return this.native.delete(url);
} catch (e: any) {
throw this.parseError(e);
}
}
}
Using HTTP Flags for Request Configuration
import { HttpFlags } from '@decaf-ts/for-http';
// Create custom HTTP flags with headers
const flags: HttpFlags = {
headers: {
'Authorization': 'Bearer token123',
'Content-Type': 'application/json'
}
};
// Use flags with repository operations
const user = await userRepository.findById('123', { flags });
// Use flags with service operations
const createdUser = await userService.create(newUser, { flags });
Complete Application Example
import axios from 'axios';
import {
AxiosHttpAdapter,
RestRepository,
RestService,
HttpConfig
} from '@decaf-ts/for-http';
import { Model } from '@decaf-ts/decorator-validation';
// Define a model
class Product extends Model {
id: string;
name: string;
price: number;
constructor(data?: Partial<Product>) {
super();
Object.assign(this, data);
}
}
// Configure the HTTP adapter
const config: HttpConfig = {
protocol: 'https',
host: 'api.mystore.com'
};
// Create the adapter
const adapter = new AxiosHttpAdapter(axios.create(), config);
// Create a repository
const productRepo = new RestRepository(adapter, Product);
// Create a service
const productService = new RestService(adapter, Product);
// Example application
async function manageProducts() {
try {
// Create a new product
const newProduct = new Product({
name: 'Smartphone',
price: 699.99
});
const createdProduct = await productRepo.create(newProduct);
console.log('Created product:', createdProduct);
// Get all products
const products = await productRepo.findAll();
console.log('All products:', products);
// Update a product
createdProduct.price = 649.99;
const updatedProduct = await productService.update(createdProduct);
console.log('Updated product:', updatedProduct);
// Delete a product
await productService.delete(createdProduct.id);
console.log('Product deleted');
// Bulk operations
const bulkProducts = [
new Product({ name: 'Laptop', price: 1299.99 }),
new Product({ name: 'Tablet', price: 499.99 })
];
const createdProducts = await productService.createAll(bulkProducts);
console.log('Created multiple products:', createdProducts);
} catch (error) {
console.error('Error managing products:', error);
}
}
manageProducts();
Related
Social
Languages
Getting help
If you have bug reports, questions or suggestions please create a new issue.
Contributing
I am grateful for any contributions made to this project. Please read this to get started.
Supporting
The first and easiest way you can support it is by Contributing. Even just finding a typo in the documentation is important.
Financial support is always welcome and helps keep both me and the project alive and healthy.
So if you can, if this project in any way. either by learning something or simply by helping you save precious time, please consider donating.
License
This project is released under the MIT License.
By developers, for developers...