nestjs-httpf
v0.1.2
Published
HTTP with functional style for Nest framework
Readme
nestjs-httpf
Functional-style HTTP client for NestJS
Features
- Functional Style: Handle complex API requests safely and declaratively
- Language-Friendly: Promise-based instead of Observable, enabling direct use of async/await
- AsyncIterable-Based: Leverages FxTS's AsyncIterable for efficient data processing through lazy evaluation and function composition
- Retry Support: Automatically retry failed requests
- Error Handling: Use
catchErrorto treat errors as values instead of try-catch - FxTS Integration: Access various helper functions from the FxTS library
- Got-Based: Uses the powerful and reliable Got HTTP client
Background
NestJS's built-in HTTP module provides an excellent way to safely handle complex API processing by applying RxJS's reactive programming. However, since it returns RxJS Observable objects, you need to extract values through operators like firstValueFrom, which can be cumbersome.
nestjs-httpf solves this inconvenience and allows you to handle asynchronous operations in a more language-friendly way:
- ✅ Directly work with Promises, enabling
awaitusage - ✅ Write asynchronous processing declaratively with functional style
- ✅ Compose functions with the FxTS library for various features
Installation
npm install nestjs-httpf @fxts/coreor
yarn add nestjs-httpf @fxts/coreor
pnpm add nestjs-httpf @fxts/coreQuick Start
1. Register the Module
import { Module } from '@nestjs/common';
import { HttpfModule } from 'nestjs-httpf';
@Module({
imports: [HttpfModule],
})
export class AppModule {}2. Use in a Service
import { Injectable } from '@nestjs/common';
import { HttpfService } from 'nestjs-httpf';
@Injectable()
export class UserService {
constructor(private readonly httpfService: HttpfService) {}
async getUser(id: string) {
const user = await this.httpfService
.get<User>(`https://api.example.com/users/${id}`)
.chain(pluck('body'))
.head();
return user;
}
}Usage Examples
Basic HTTP Requests
// GET request
const data = await this.httpfService
.get<{ message: string }>('https://api.example.com/hello')
.chain(pluck('body'))
.head();
// POST request
const result = await this.httpfService
.post<{ id: string }>('https://api.example.com/users', {
json: { name: 'John', email: '[email protected]' },
})
.chain(pluck('body'))
.head();
// PUT request
await this.httpfService.put('https://api.example.com/users/123', {
json: { name: 'Jane' },
});
// PATCH request
await this.httpfService.patch('https://api.example.com/users/123', {
json: { email: '[email protected]' },
});
// DELETE request
await this.httpfService.delete('https://api.example.com/users/123');Error Handling
Use catchError to treat errors as values:
const result = await this.httpfService
.get<User>('https://api.example.com/users/123')
.catchError((error) => ({
body: null,
statusCode: 500,
error: error.message,
}))
.map((response) => response.body)
.head();
// Transform and handle errors
const user = await this.httpfService
.get<User>('https://api.example.com/users/123')
.catchError((error) => {
console.error('Failed to fetch user:', error);
return { body: { id: '0', name: 'Unknown' } };
})
.map((response) => response.body)
.head();Retry
Automatically retry failed requests:
// Retry up to 3 times
const data = await this.httpfService
.get<Data>('https://api.example.com/unstable-endpoint')
.retry(3)
.map((response) => response.body)
.head();
// Using retry with catchError
const result = await this.httpfService
.get<Data>('https://api.example.com/data')
.retry(2)
.catchError((error) => ({
body: { fallback: true },
statusCode: 200,
}))
.map((response) => response.body)
.head();Using FxTS Methods
You can use various helper functions from FxTS:
// filter: Process only responses that match conditions
const successResponse = await this.httpfService
.get<Data>('https://api.example.com/data')
.filter((response) => response.statusCode === 200)
.map((response) => response.body)
.head();
// take: Get only the first N items
const items = await this.httpfService
.get<Item[]>('https://api.example.com/items')
.map((response) => response.body)
.take(5)
.toArray();
// Complex chaining
const processedData = await this.httpfService
.get<RawData>('https://api.example.com/data')
.retry(2)
.catchError(() => ({ body: [] }))
.filter((response) => response.statusCode === 200)
.map((response) => response.body)
.map((data) => data.map((item) => ({ ...item, processed: true })))
.head();Flattening Data with mergeMap
const allItems = await this.httpfService
.get<{ items: Item[] }>('https://api.example.com/data')
.map((response) => response.body)
.mergeMap((data) => data.items)
.toArray();Advanced Configuration
Global Configuration
import { Module } from '@nestjs/common';
import { HttpfModule } from 'nestjs-httpf';
@Module({
imports: [
HttpfModule.register({
global: true,
timeout: 5000,
retry: {
limit: 2,
},
headers: {
'User-Agent': 'my-app/1.0.0',
},
}),
],
})
export class AppModule {}Async Configuration
import { Module } from '@nestjs/common';
import { HttpfModule } from 'nestjs-httpf';
import { ConfigModule, ConfigService } from '@nestjs/config';
@Module({
imports: [
HttpfModule.registerAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
timeout: configService.get('HTTP_TIMEOUT'),
headers: {
'API-Key': configService.get('API_KEY'),
},
}),
inject: [ConfigService],
}),
],
})
export class AppModule {}Configuration with useClass
import { Injectable } from '@nestjs/common';
import { HttpfModuleOptionsFactory, HttpfModuleOptions } from 'nestjs-httpf';
@Injectable()
class HttpConfigService implements HttpfModuleOptionsFactory {
createHttpOptions(): HttpfModuleOptions {
return {
timeout: 5000,
retry: {
limit: 3,
},
};
}
}
@Module({
imports: [
HttpfModule.registerAsync({
useClass: HttpConfigService,
}),
],
})
export class AppModule {}API Reference
HttpfService
Methods
get<T>(url: string | URL, options?: OptionsOfJSONResponseBody): HttpfAsyncIterable<Response<T>>post<T>(url: string | URL, options?: OptionsOfJSONResponseBody): HttpfAsyncIterable<Response<T>>put<T>(url: string | URL, options?: OptionsOfJSONResponseBody): HttpfAsyncIterable<Response<T>>patch<T>(url: string | URL, options?: OptionsOfJSONResponseBody): HttpfAsyncIterable<Response<T>>delete<T>(url: string | URL, options?: OptionsOfJSONResponseBody): HttpfAsyncIterable<Response<T>>head<T>(url: string | URL, options?: OptionsOfJSONResponseBody): HttpfAsyncIterable<Response<T>>
HttpfAsyncIterable
HttpfAsyncIterable provides all FxTS methods along with the following additional methods:
Additional Methods
catchError<E>(handler: (error: unknown) => E): HttpfAsyncIterable<T | E>- Catch errors and return a fallback valueretry(retries: number): HttpfAsyncIterable<T>- Retry the specified number of times on failuremergeMap<U>(mapper: (value: T) => Iterable<U>): HttpfAsyncIterable<U>- Flatten values
FxTS Methods and Helper Functions
See the FxTS documentation for more information.
License
This project is licensed under the MIT License.
Third-Party Licenses
This project uses the following third-party libraries:
| Library | License | | ------------------------------------------------ | ---------- | | @fxts/core | Apache-2.0 | | got | MIT | | @nestjs/common | MIT |
Apache-2.0 License Notice
This project depends on @fxts/core which is licensed under the Apache License 2.0. When using this library, you must comply with the Apache-2.0 license terms for @fxts/core, which include:
- Attribution: You must give appropriate credit and include a copy of the Apache-2.0 license
- State Changes: If you modify
@fxts/core, you must state the changes made - NOTICE Preservation: If
@fxts/coreincludes a NOTICE file, you must include it in your distribution - Patent Grant: Apache-2.0 includes an express grant of patent rights from contributors
For the full Apache-2.0 license text, see: https://www.apache.org/licenses/LICENSE-2.0
Contributing
Issues and pull requests are always welcome!
