├── lib ├── interfaces │ ├── index.ts │ └── http-module.interface.ts ├── index.ts ├── http.constants.ts ├── http.service.ts └── http.module.ts ├── .prettierrc ├── .gitignore ├── tsconfig.json ├── .github ├── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md └── workflows │ └── publish-master.yml ├── eslint.config.js ├── LICENSE ├── package.json └── README.md /lib/interfaces/index.ts: -------------------------------------------------------------------------------- 1 | export * from './http-module.interface'; 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | 2 | { 3 | "trailingComma": "all", 4 | "singleQuote": true, 5 | "arrowParens": "avoid" 6 | } -------------------------------------------------------------------------------- /lib/index.ts: -------------------------------------------------------------------------------- 1 | export { HttpService } from './http.service'; 2 | export { HttpModule } from './http.module'; 3 | export * from './interfaces'; 4 | -------------------------------------------------------------------------------- /lib/http.constants.ts: -------------------------------------------------------------------------------- 1 | export const AXIOS_INSTANCE_TOKEN = 'AXIOS_INSTANCE_TOKEN'; 2 | export const HTTP_MODULE_ID = 'HTTP_MODULE_ID'; 3 | export const HTTP_MODULE_OPTIONS = 'HTTP_MODULE_OPTIONS'; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # IDE 5 | /.idea 6 | /.awcache 7 | /.vscode 8 | 9 | # misc 10 | npm-debug.log 11 | .DS_Store 12 | 13 | # tests 14 | /test 15 | /coverage 16 | /.nyc_output 17 | 18 | # dist 19 | dist -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "declaration": true, 5 | "removeComments": true, 6 | "noLib": false, 7 | "emitDecoratorMetadata": true, 8 | "esModuleInterop": true, 9 | "experimentalDecorators": true, 10 | "target": "es6", 11 | "sourceMap": false, 12 | "outDir": "./dist", 13 | "rootDir": "./lib", 14 | "skipLibCheck": true, 15 | }, 16 | "include": ["lib/**/*"], 17 | "exclude": ["node_modules"] 18 | } 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /lib/interfaces/http-module.interface.ts: -------------------------------------------------------------------------------- 1 | import { ModuleMetadata, Provider, Type } from '@nestjs/common'; 2 | import { AxiosRequestConfig } from 'axios'; 3 | import { IAxiosRetryConfig } from 'axios-retry'; 4 | 5 | export type HttpModuleOptions = AxiosRequestConfig & IAxiosRetryConfig; 6 | 7 | export interface HttpModuleOptionsFactory { 8 | createHttpOptions(): Promise | HttpModuleOptions; 9 | } 10 | 11 | export interface HttpModuleAsyncOptions 12 | extends Pick { 13 | useExisting?: Type; 14 | useClass?: Type; 15 | useFactory?: ( 16 | ...args: any[] 17 | ) => Promise | HttpModuleOptions; 18 | inject?: any[]; 19 | extraProviders?: Provider[]; 20 | } 21 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: './tsconfig.json', 5 | sourceType: 'module', 6 | }, 7 | plugins: ['@typescript-eslint/eslint-plugin'], 8 | extends: [ 9 | 'plugin:@typescript-eslint/eslint-recommended', 10 | 'plugin:@typescript-eslint/recommended', 11 | 'prettier', 12 | ], 13 | root: true, 14 | env: { 15 | node: true, 16 | jest: true, 17 | }, 18 | rules: { 19 | '@typescript-eslint/interface-name-prefix': 'off', 20 | '@typescript-eslint/explicit-function-return-type': 'off', 21 | '@typescript-eslint/no-explicit-any': 'off', 22 | '@typescript-eslint/no-use-before-define': 'off', 23 | '@typescript-eslint/no-non-null-assertion': 'off', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /lib/http.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Inject } from '@nestjs/common'; 2 | import Axios, { AxiosInstance } from 'axios'; 3 | import { AXIOS_INSTANCE_TOKEN } from './http.constants'; 4 | 5 | @Injectable() 6 | export class HttpService { 7 | public readonly put: typeof Axios.put; 8 | public readonly post: typeof Axios.post; 9 | public readonly patch: typeof Axios.patch; 10 | public readonly head: typeof Axios.patch; 11 | public readonly delete: typeof Axios.delete; 12 | public readonly get: typeof Axios.get; 13 | public readonly request: typeof Axios.request; 14 | 15 | constructor( 16 | @Inject(AXIOS_INSTANCE_TOKEN) 17 | private readonly instance: AxiosInstance = Axios, 18 | ) { 19 | this.put = this.instance.put; 20 | this.post = this.instance.post; 21 | this.patch = this.instance.patch; 22 | this.head = this.instance.head; 23 | this.head = this.instance.head; 24 | this.delete = this.instance.delete; 25 | this.get = this.instance.get; 26 | this.request = this.instance.request; 27 | } 28 | 29 | get axiosRef(): AxiosInstance { 30 | return this.instance; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 benhason1 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/publish-master.yml: -------------------------------------------------------------------------------- 1 | name: publish-and-tag 2 | env: 3 | CI: true 4 | on: 5 | release: 6 | types: [published] 7 | jobs: 8 | publish-to-npm: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: checkout 12 | uses: actions/checkout@v2 13 | - name: setup Node 14 | uses: actions/setup-node@v2 15 | with: 16 | node-version: 18.x 17 | registry-url: 'https://registry.npmjs.org' 18 | - name: install 19 | run: npm install 20 | # Publish to npm if this version is not published 21 | - name: publish 22 | run: npm run publish:npm 23 | env: 24 | NODE_AUTH_TOKEN: ${{ secrets.NPM_AUTH_TOKEN }} 25 | create-tag: 26 | runs-on: ubuntu-latest 27 | needs: publish-to-npm 28 | steps: 29 | - name: checkout 30 | uses: actions/checkout@v2 31 | # Push tag to GitHub if package.json version's tag is not tagged 32 | - name: package-version 33 | run: node -p -e '`PACKAGE_VERSION=${require("./package.json").version}`' >> $GITHUB_ENV 34 | - name: package-version-to-git-tag 35 | uses: pkgdeps/git-tag-action@v2 36 | with: 37 | github_token: ${{ secrets.GITHUB_TOKEN }} 38 | github_repo: ${{ github.repository }} 39 | version: ${{ env.PACKAGE_VERSION }} 40 | git_commit_sha: ${{ github.sha }} 41 | git_tag_prefix: "v" 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nestjs-http-promise", 3 | "version": "4.0.0", 4 | "keywords": [ 5 | "nestjs", 6 | "http", 7 | "promise", 8 | "retry", 9 | "retries", 10 | "axios" 11 | ], 12 | "description": "promise implementation of nestjs http module with retries feature using axios-retry and axios", 13 | "author": "Ben Hason", 14 | "license": "MIT", 15 | "main": "dist/index.js", 16 | "types": "dist/index.d.ts", 17 | "url": "https://github.com/benhason1/nestjs-http-promise#readme", 18 | "scripts": { 19 | "build": "rimraf -rf dist && tsc -p tsconfig.json", 20 | "format": "prettier --write \"lib/**/*.ts\"", 21 | "lint": "eslint 'lib/**/*.ts' --fix", 22 | "prepare": "npm run build", 23 | "prepublish:npm": "npm run build", 24 | "publish:npm": "npm publish --access public", 25 | "publish:beta": "npm publish --access public --tag beta", 26 | "prepublish:beta": "npm run build" 27 | }, 28 | "dependencies": { 29 | "axios-retry": "^4.5.0" 30 | }, 31 | "devDependencies": { 32 | "@nestjs/common": "^11.0.7", 33 | "@nestjs/core": "^11.0.7", 34 | "@nestjs/platform-express": "^11.0.7", 35 | "@types/node": "^22.13.0", 36 | "@typescript-eslint/eslint-plugin": "^8.22.0", 37 | "@typescript-eslint/parser": "^8.22.0", 38 | "eslint": "^9.19.0", 39 | "eslint-config-prettier": "^10.0.1", 40 | "eslint-plugin-import": "^2.31.0", 41 | "husky": "^9.1.7", 42 | "lint-staged": "^15.4.3", 43 | "prettier": "^3.4.2", 44 | "reflect-metadata": "^0.2.2", 45 | "rimraf": "^6.0.1", 46 | "typescript": "^5.7.3" 47 | }, 48 | "peerDependencies": { 49 | "@nestjs/common": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0", 50 | "reflect-metadata": "^0.2.2", 51 | "axios": "^1.4.0" 52 | }, 53 | "lint-staged": { 54 | "*.ts": [ 55 | "prettier --write" 56 | ] 57 | }, 58 | "husky": { 59 | "hooks": { 60 | "pre-commit": "lint-staged" 61 | } 62 | }, 63 | "repository": { 64 | "type": "git", 65 | "url": "https://github.com/benhason1/nestjs-http-promise" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /lib/http.module.ts: -------------------------------------------------------------------------------- 1 | import { DynamicModule, Module, Provider } from '@nestjs/common'; 2 | import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util'; 3 | import Axios from 'axios'; 4 | import { 5 | AXIOS_INSTANCE_TOKEN, 6 | HTTP_MODULE_ID, 7 | HTTP_MODULE_OPTIONS, 8 | } from './http.constants'; 9 | import { HttpService } from './http.service'; 10 | import { 11 | HttpModuleAsyncOptions, 12 | HttpModuleOptions, 13 | HttpModuleOptionsFactory, 14 | } from './interfaces'; 15 | import axiosRetry from 'axios-retry'; 16 | 17 | const createAxiosInstance = (config?: HttpModuleOptions) => { 18 | const axiosInstance = Axios.create(config); 19 | axiosRetry(axiosInstance, config); 20 | return axiosInstance; 21 | }; 22 | 23 | @Module({ 24 | providers: [ 25 | HttpService, 26 | { 27 | provide: AXIOS_INSTANCE_TOKEN, 28 | useValue: createAxiosInstance(), 29 | }, 30 | ], 31 | exports: [HttpService], 32 | }) 33 | export class HttpModule { 34 | static register(config: HttpModuleOptions): DynamicModule { 35 | return { 36 | module: HttpModule, 37 | providers: [ 38 | { 39 | provide: AXIOS_INSTANCE_TOKEN, 40 | useValue: createAxiosInstance(config), 41 | }, 42 | { 43 | provide: HTTP_MODULE_ID, 44 | useValue: randomStringGenerator(), 45 | }, 46 | ], 47 | }; 48 | } 49 | 50 | static registerAsync(options: HttpModuleAsyncOptions): DynamicModule { 51 | return { 52 | module: HttpModule, 53 | imports: options.imports, 54 | providers: [ 55 | ...this.createAsyncProviders(options), 56 | { 57 | provide: AXIOS_INSTANCE_TOKEN, 58 | useFactory: (config: HttpModuleOptions) => 59 | createAxiosInstance(config), 60 | inject: [HTTP_MODULE_OPTIONS], 61 | }, 62 | { 63 | provide: HTTP_MODULE_ID, 64 | useValue: randomStringGenerator(), 65 | }, 66 | ...(options.extraProviders || []), 67 | ], 68 | }; 69 | } 70 | 71 | private static createAsyncProviders( 72 | options: HttpModuleAsyncOptions, 73 | ): Provider[] { 74 | if (options.useExisting || options.useFactory) { 75 | return [this.createAsyncOptionsProvider(options)]; 76 | } 77 | 78 | const providers = [this.createAsyncOptionsProvider(options)]; 79 | 80 | if (options.useClass) 81 | providers.push({ 82 | provide: options.useClass, 83 | useClass: options.useClass, 84 | }); 85 | 86 | return providers; 87 | } 88 | 89 | private static createAsyncOptionsProvider( 90 | options: HttpModuleAsyncOptions, 91 | ): Provider { 92 | if (options.useFactory) { 93 | return { 94 | provide: HTTP_MODULE_OPTIONS, 95 | useFactory: options.useFactory, 96 | inject: options.inject || [], 97 | }; 98 | } 99 | 100 | let inject; 101 | if (options.useExisting) inject = [options.useExisting]; 102 | else if (options.useClass) inject = [options.useClass]; 103 | 104 | return { 105 | provide: HTTP_MODULE_OPTIONS, 106 | useFactory: async (optionsFactory: HttpModuleOptionsFactory) => 107 | optionsFactory.createHttpOptions(), 108 | inject, 109 | }; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nestjs-http-promise 2 | [![npm version](https://img.shields.io/npm/v/nestjs-http-promise.svg?style=flat-square)](https://www.npmjs.org/package/nestjs-http-promise) 3 | [![npm downloads](https://img.shields.io/npm/dm/nestjs-http-promise.svg?style=flat-square)](http://npm-stat.com/charts.html?package=nestjs-http-promise) 4 | 5 | Buy Me A Coffee 6 | 7 | 8 | 9 | 10 | ## description 11 | nestjs module that just doing little modification to the original and good **nestjs** http module. 12 | 13 | 14 | ## features 15 | * axios - the most used package for http requests in npm and the one used by nestjs official http library. 16 | * better axios stack trace - axios has an [open issue](https://github.com/axios/axios/issues/2387) about improvement of their stack trace. 17 | in this library there is a default interceptor that will intercept the stack trace and will add data to it. 18 | * promise based - most of us using the current http module that uses observable which we don't use most of the time 19 | and in order to avoid it were just calling `.toPromise()` every http call. 20 | * retries - in many cases we will want to retry a failing http call. 21 | with observable we could just add the retry operator (rxjs) but with promises we need to implement this logic ourselves. 22 | this package will make it easy for you, just pass `{ retries: NUMBER_OF_RETRIES }` in the config of the http module. 23 | **more details in the configuration section** 24 | 25 | ## quick start 26 | ### installing 27 | Using npm: 28 | ``` 29 | $ npm install nestjs-http-promise 30 | ``` 31 | 32 | Using yarn: 33 | ``` 34 | $ yarn add nestjs-http-promise 35 | ``` 36 | 37 | ### usage - just like every nest.js module 38 | import the module: 39 | ```ts 40 | import { HttpModule } from 'nestjs-http-promise' 41 | 42 | @Module({ 43 | imports: [HttpModule] 44 | }) 45 | ``` 46 | 47 | inject the service in the class: 48 | ```ts 49 | import { HttpService } from 'nestjs-http-promise' 50 | 51 | class Demo { 52 | constructor(private readonly httpService: HttpService) {} 53 | } 54 | ``` 55 | 56 | use the service: 57 | ```ts 58 | public callSomeServer(): Promise { 59 | return this.httpService.get('http://fakeService') 60 | } 61 | ``` 62 | 63 | ## configuration 64 | 65 | the service uses axios and axios-retry, so you can pass any [AxiosRequestConfig](https://github.com/axios/axios#request-config) 66 | And/Or [AxiosRetryConfig](https://github.com/softonic/axios-retry#options) 67 | 68 | just pass it in the `.register()` method as you would do in the original nestjs httpModule 69 | ```ts 70 | import { HttpModule } from 'nestjs-http-promise' 71 | 72 | @Module({ 73 | imports: [HttpModule.register( 74 | { 75 | timeout: 1000, 76 | retries: 5, 77 | ... 78 | } 79 | )] 80 | }) 81 | ``` 82 | 83 | ### default configuration 84 | * default config of axios-retry : https://github.com/softonic/axios-retry#options 85 | * better axios stack trace is added by default, you can turn it off by passing the **isBetterStackTraceEnabled** to false. 86 | 87 | ## async configuration 88 | When you need to pass module options asynchronously instead of statically, use the `registerAsync()` method **just like in nest httpModule**. 89 | 90 | you have a couple of techniques to do it: 91 | * with the useFactory 92 | ```ts 93 | HttpModule.registerAsync({ 94 | useFactory: () => ({ 95 | timeout: 1000, 96 | retries: 5, 97 | ... 98 | }), 99 | }); 100 | ``` 101 | 102 | * using class 103 | 104 | ```ts 105 | HttpModule.registerAsync({ 106 | useClass: HttpConfigService, 107 | }); 108 | ``` 109 | Note that in this example, the HttpConfigService has to implement HttpModuleOptionsFactory interface as shown below. 110 | ```ts 111 | @Injectable() 112 | class HttpConfigService implements HttpModuleOptionsFactory { 113 | async createHttpOptions(): Promise { 114 | const configurationData = await someAsyncMethod(); 115 | return { 116 | timeout: configurationData.timeout, 117 | retries: 5, 118 | ... 119 | }; 120 | } 121 | } 122 | ``` 123 | If you want to reuse an existing options provider instead of creating a copy inside the HttpModule, 124 | use the useExisting syntax. 125 | ```ts 126 | HttpModule.registerAsync({ 127 | imports: [ConfigModule], 128 | useExisting: ConfigService, 129 | }); 130 | ``` 131 | --------------------------------------------------------------------------------