Skip to content

Commit f31546b

Browse files
jbl428imdudu1
andcommitted
feat: implement fetch http client
Co-authored-by: imdudu1 <[email protected]>
1 parent 62d3df7 commit f31546b

File tree

7 files changed

+418
-2
lines changed

7 files changed

+418
-2
lines changed

lib/http-interface.module.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,41 @@
11
import { type DynamicModule } from '@nestjs/common';
2-
import { DiscoveryModule } from '@nestjs/core';
2+
import {
3+
DiscoveryModule,
4+
DiscoveryService,
5+
MetadataScanner,
6+
} from '@nestjs/core';
7+
import { FetchHttpClient } from './supports/fetch-http-client';
38
import { NodeFetchInjector } from './supports/node-fetch.injector';
9+
import { type HttpClient } from './types';
410

511
export interface HttpInterfaceConfig {
612
timeout?: number;
13+
httpClient?: HttpClient;
714
}
815

916
export class HttpInterfaceModule {
1017
static register(config: HttpInterfaceConfig): DynamicModule {
1118
return {
1219
imports: [DiscoveryModule],
1320
module: HttpInterfaceModule,
14-
providers: [NodeFetchInjector],
21+
providers: [
22+
{
23+
provide: NodeFetchInjector,
24+
inject: [MetadataScanner, DiscoveryService],
25+
useFactory: (
26+
metadataScanner: MetadataScanner,
27+
discoveryService: DiscoveryService,
28+
) => {
29+
const timeout = config.timeout ?? 5000;
30+
31+
return new NodeFetchInjector(
32+
metadataScanner,
33+
discoveryService,
34+
config.httpClient ?? new FetchHttpClient(timeout),
35+
);
36+
},
37+
},
38+
],
1539
};
1640
}
1741
}

lib/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from './decorators';
22
export * from './supports';
3+
export * from './types';
34
export * from './http-interface.module';
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { type AddressInfo } from 'node:net';
2+
import Fastify, { type FastifyInstance } from 'fastify';
3+
import { describe, test, expect, beforeAll, afterAll } from 'vitest';
4+
import { FetchHttpClient } from './fetch-http-client';
5+
6+
describe('FetchHttpClient', () => {
7+
let fastify: FastifyInstance;
8+
9+
beforeAll(async () => {
10+
fastify = Fastify({ logger: false });
11+
12+
fastify.get('/', async (request, reply) => {
13+
await reply.send({ hello: 'world' });
14+
});
15+
16+
fastify.get('/timeout', async (request, reply) => {
17+
await new Promise((resolve) => setTimeout(resolve, 10000));
18+
await reply.send({ hello: 'world' });
19+
});
20+
21+
await fastify.listen({ port: 0 });
22+
});
23+
24+
afterAll(async () => {
25+
await fastify.close();
26+
});
27+
28+
test('should request successfully', async () => {
29+
// given
30+
const address = fastify.server.address() as AddressInfo;
31+
const httpClient = new FetchHttpClient(5000);
32+
const request = new Request(`http://localhost:${address.port}/`);
33+
34+
// when
35+
const response = await httpClient.request(request);
36+
37+
// then
38+
expect(await response.json()).toEqual({ hello: 'world' });
39+
});
40+
41+
test('should throw error when timeout', async () => {
42+
// given
43+
const address = fastify.server.address() as AddressInfo;
44+
const httpClient = new FetchHttpClient(3000);
45+
const request = new Request(`http://localhost:${address.port}/timeout`);
46+
47+
// when
48+
const doRequest = async (): Promise<Response> =>
49+
await httpClient.request(request);
50+
51+
// then
52+
await expect(doRequest).rejects.toThrowError('Request Timeout: 3000ms');
53+
});
54+
});

lib/supports/fetch-http-client.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { type HttpClient } from '../types/http-client.interface';
2+
3+
export class FetchHttpClient implements HttpClient {
4+
#timeout: number;
5+
6+
constructor(timeout: number) {
7+
this.#timeout = timeout;
8+
}
9+
10+
async request(request: Request): Promise<Response> {
11+
const controller = new AbortController();
12+
13+
return await Promise.race<Response>([
14+
fetch(request, { signal: controller.signal }),
15+
new Promise((resolve, reject) =>
16+
setTimeout(() => {
17+
controller.abort();
18+
reject(new Error(`Request Timeout: ${this.#timeout}ms`));
19+
}, this.#timeout),
20+
),
21+
]);
22+
}
23+
}

lib/types/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
export * from './http-client.interface';
2+
export * from './http-method';

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"eslint-config-standard-with-typescript": "^34.0.1",
3838
"eslint-plugin-import": "^2.27.5",
3939
"eslint-plugin-prettier": "^4.2.1",
40+
"fastify": "^4.15.0",
4041
"husky": "^8.0.3",
4142
"lint-staged": "^13.2.1",
4243
"prettier": "^2.8.7",

0 commit comments

Comments
 (0)