Functional-style HTTP client for NestJS
- 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
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
npm install nestjs-httpf @fxts/coreor
yarn add nestjs-httpf @fxts/coreor
pnpm add nestjs-httpf @fxts/coreimport { Module } from '@nestjs/common';
import { HttpfModule } from 'nestjs-httpf';
@Module({
imports: [HttpfModule],
})
export class AppModule {}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;
}
}// 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: 'john@example.com' },
})
.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: 'jane@example.com' },
});
// DELETE request
await this.httpfService.delete('https://api.example.com/users/123');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();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();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();const allItems = await this.httpfService
.get<{ items: Item[] }>('https://api.example.com/data')
.map((response) => response.body)
.mergeMap((data) => data.items)
.toArray();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 {}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 {}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 {}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 provides all FxTS methods along with the following 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
See the FxTS documentation for more information.
This project is licensed under the MIT License.
This project uses the following third-party libraries:
| Library | License |
|---|---|
| @fxts/core | Apache-2.0 |
| got | MIT |
| @nestjs/common | MIT |
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
Issues and pull requests are always welcome!