nestjs-boost 0.0.0-stage → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/LICENSE +21 -0
  3. package/NOTICE.md +6 -0
  4. package/README.md +168 -2
  5. package/bin/nestjs-boost.mjs +148 -0
  6. package/guidelines/core.md +64 -0
  7. package/lib/agents.mjs +137 -0
  8. package/lib/installer.mjs +450 -0
  9. package/lib/prompt.mjs +131 -0
  10. package/lib/setup.mjs +60 -0
  11. package/manifest.json +676 -0
  12. package/package.json +67 -6
  13. package/scripts/postinstall.mjs +49 -0
  14. package/skills/nestjs-ai/SKILL.md +145 -0
  15. package/skills/nestjs-ai/references/ai-reference.md +143 -0
  16. package/skills/nestjs-architecture/SKILL.md +83 -0
  17. package/skills/nestjs-architecture/references/dynamic-modules.md +92 -0
  18. package/skills/nestjs-architecture/references/scopes-lifecycle.md +98 -0
  19. package/skills/nestjs-architecture/references/troubleshooting.md +77 -0
  20. package/skills/nestjs-authentication/SKILL.md +115 -0
  21. package/skills/nestjs-authentication/references/flows.md +180 -0
  22. package/skills/nestjs-authorization/SKILL.md +104 -0
  23. package/skills/nestjs-caching/SKILL.md +109 -0
  24. package/skills/nestjs-cli-tooling/SKILL.md +94 -0
  25. package/skills/nestjs-cli-tooling/references/nest-cli-json.md +91 -0
  26. package/skills/nestjs-configuration/SKILL.md +109 -0
  27. package/skills/nestjs-cqrs/SKILL.md +124 -0
  28. package/skills/nestjs-database-drizzle/SKILL.md +117 -0
  29. package/skills/nestjs-database-mikro-orm/SKILL.md +109 -0
  30. package/skills/nestjs-database-mongoose/SKILL.md +124 -0
  31. package/skills/nestjs-database-prisma/SKILL.md +126 -0
  32. package/skills/nestjs-database-sequelize/SKILL.md +88 -0
  33. package/skills/nestjs-database-typeorm/SKILL.md +110 -0
  34. package/skills/nestjs-deployment/SKILL.md +133 -0
  35. package/skills/nestjs-deployment/references/serverless-and-systemd.md +133 -0
  36. package/skills/nestjs-events/SKILL.md +78 -0
  37. package/skills/nestjs-file-storage/SKILL.md +111 -0
  38. package/skills/nestjs-file-storage/references/storage-reference.md +82 -0
  39. package/skills/nestjs-graphql/SKILL.md +119 -0
  40. package/skills/nestjs-graphql/references/federation.md +110 -0
  41. package/skills/nestjs-graphql/references/schema-features.md +133 -0
  42. package/skills/nestjs-health-checks/SKILL.md +114 -0
  43. package/skills/nestjs-http-client/SKILL.md +118 -0
  44. package/skills/nestjs-http-client/references/http-client-reference.md +116 -0
  45. package/skills/nestjs-http-platform/SKILL.md +104 -0
  46. package/skills/nestjs-http-platform/references/adapters.md +88 -0
  47. package/skills/nestjs-http-web-features/SKILL.md +113 -0
  48. package/skills/nestjs-http-web-features/references/uploads-streaming.md +108 -0
  49. package/skills/nestjs-i18n/SKILL.md +113 -0
  50. package/skills/nestjs-idempotency/SKILL.md +112 -0
  51. package/skills/nestjs-locks/SKILL.md +108 -0
  52. package/skills/nestjs-logging/SKILL.md +123 -0
  53. package/skills/nestjs-mail/SKILL.md +125 -0
  54. package/skills/nestjs-mail/references/mail-reference.md +90 -0
  55. package/skills/nestjs-mcp-server/SKILL.md +152 -0
  56. package/skills/nestjs-mcp-server/references/mcp-reference.md +146 -0
  57. package/skills/nestjs-microservices/SKILL.md +108 -0
  58. package/skills/nestjs-microservices/references/transports.md +117 -0
  59. package/skills/nestjs-observability/SKILL.md +108 -0
  60. package/skills/nestjs-observability/references/apm-and-otel.md +106 -0
  61. package/skills/nestjs-openapi/SKILL.md +90 -0
  62. package/skills/nestjs-openapi/references/swagger-reference.md +180 -0
  63. package/skills/nestjs-outbox/SKILL.md +119 -0
  64. package/skills/nestjs-outbox/references/outbox-reference.md +74 -0
  65. package/skills/nestjs-passport-auth/SKILL.md +125 -0
  66. package/skills/nestjs-queues/SKILL.md +118 -0
  67. package/skills/nestjs-queues/references/bull-legacy.md +75 -0
  68. package/skills/nestjs-request-pipeline/SKILL.md +146 -0
  69. package/skills/nestjs-request-pipeline/references/filters-interceptors.md +114 -0
  70. package/skills/nestjs-request-pipeline/references/pipes-decorators.md +80 -0
  71. package/skills/nestjs-resilience/SKILL.md +116 -0
  72. package/skills/nestjs-resilience/references/resilience-reference.md +74 -0
  73. package/skills/nestjs-scheduling/SKILL.md +100 -0
  74. package/skills/nestjs-security-hardening/SKILL.md +99 -0
  75. package/skills/nestjs-security-hardening/references/throttler-advanced.md +78 -0
  76. package/skills/nestjs-standalone-apps/SKILL.md +78 -0
  77. package/skills/nestjs-testing/SKILL.md +118 -0
  78. package/skills/nestjs-testing/references/advanced-testing.md +62 -0
  79. package/skills/nestjs-upgrading/SKILL.md +82 -0
  80. package/skills/nestjs-upgrading/references/esm-switch.md +114 -0
  81. package/skills/nestjs-validation-serialization/SKILL.md +124 -0
  82. package/skills/nestjs-validation-serialization/references/validation-options.md +84 -0
  83. package/skills/nestjs-webhooks/SKILL.md +116 -0
  84. package/skills/nestjs-webhooks/references/webhooks-reference.md +132 -0
  85. package/skills/nestjs-websockets/SKILL.md +108 -0
  86. package/skills/nestjs-workflows/SKILL.md +123 -0
  87. package/skills/nestjs-workflows/references/workflows-reference.md +85 -0
@@ -0,0 +1,110 @@
1
+ # GraphQL federation
2
+
3
+ ## Contents
4
+ - Packages and drivers
5
+ - Users subgraph (owns the entity)
6
+ - Posts subgraph (references and extends it)
7
+ - Federation 2
8
+ - Gateway
9
+ - Mercurius federation
10
+ - Subscriptions and hiding operations
11
+
12
+ ## Packages and drivers
13
+
14
+ | Role | Apollo | Mercurius |
15
+ | --- | --- | --- |
16
+ | Subgraph | `npm i @apollo/subgraph`; `ApolloFederationDriver` / `ApolloFederationDriverConfig` from `@nestjs/apollo` | `npm i @apollo/subgraph @nestjs/mercurius`; `MercuriusFederationDriver` |
17
+ | Gateway | `npm i @apollo/gateway`; `ApolloGatewayDriver` / `ApolloGatewayDriverConfig` | `npm i @mercuriusjs/gateway`; `MercuriusGatewayDriver` |
18
+
19
+ ## Users subgraph (owns the entity)
20
+
21
+ ```typescript
22
+ @ObjectType()
23
+ @Directive('@key(fields: "id")')
24
+ export class User {
25
+ @Field(() => ID) id: number;
26
+ @Field() name: string;
27
+ }
28
+
29
+ @Resolver(() => User)
30
+ export class UsersResolver {
31
+ constructor(private readonly users: UsersService) {}
32
+ @Query(() => User) getUser(@Args('id') id: number) { return this.users.findById(id); }
33
+ @ResolveReference()
34
+ resolveReference(reference: { __typename: string; id: number }) { return this.users.findById(reference.id); } // called by the gateway
35
+ }
36
+
37
+ GraphQLModule.forRoot<ApolloFederationDriverConfig>({ driver: ApolloFederationDriver, autoSchemaFile: true }),
38
+ ```
39
+
40
+ Schema first: `type User @key(fields: "id") { … }` and `extend type Query { … }` with `typePaths`.
41
+
42
+ ## Posts subgraph (references and extends it)
43
+
44
+ ```typescript
45
+ @ObjectType()
46
+ @Directive('@extends') // Federation 1 only
47
+ @Directive('@key(fields: "id")')
48
+ export class User {
49
+ @Field(() => ID) @Directive('@external') id: number; // Federation 1 only
50
+ @Field(() => [Post]) posts?: Post[];
51
+ }
52
+
53
+ @Resolver(() => User)
54
+ export class UsersResolver {
55
+ @ResolveField(() => [Post]) posts(@Parent() user: User) { return this.posts.forAuthor(user.id); }
56
+ }
57
+
58
+ @Resolver(() => Post)
59
+ export class PostsResolver {
60
+ @ResolveField(() => User) user(@Parent() post: Post) { return { __typename: 'User', id: post.authorId }; } // reference, resolved by the Users subgraph
61
+ }
62
+
63
+ GraphQLModule.forRoot<ApolloFederationDriverConfig>({
64
+ driver: ApolloFederationDriver,
65
+ autoSchemaFile: true,
66
+ buildSchemaOptions: { orphanedTypes: [User] }, // entity only reachable via _entities
67
+ }),
68
+ ```
69
+
70
+ ## Federation 2
71
+
72
+ - Code first: `autoSchemaFile: { federation: 2 }` — Nest adds the `@link` directive.
73
+ - Schema first: `extend schema @link(url: "https://specs.apollo.dev/federation/v2.0", import: ["@key"])`.
74
+ - Entities have no originating subgraph: drop `extend`, `@extends`, `@external`, and no need to extend `Query`.
75
+ - Mercurius doesn't fully support Federation 2.
76
+
77
+ ## Gateway
78
+
79
+ ```typescript
80
+ GraphQLModule.forRoot<ApolloGatewayDriverConfig>({
81
+ driver: ApolloGatewayDriver,
82
+ server: { /* Apollo Server options, e.g. context */ },
83
+ gateway: {
84
+ supergraphSdl: new IntrospectAndCompose({
85
+ subgraphs: [
86
+ { name: 'users', url: 'http://user-service/graphql' },
87
+ { name: 'posts', url: 'http://post-service/graphql' },
88
+ ],
89
+ }),
90
+ },
91
+ }),
92
+ ```
93
+
94
+ Other gateway options (header forwarding, polling) come from `@apollo/gateway`'s own API; pass them in `gateway`.
95
+
96
+ ## Mercurius federation
97
+
98
+ ```typescript
99
+ GraphQLModule.forRoot<MercuriusFederationDriverConfig>({ driver: MercuriusFederationDriver, autoSchemaFile: true }); // subgraph (or typePaths)
100
+ GraphQLModule.forRoot<MercuriusGatewayDriverConfig>({
101
+ driver: MercuriusGatewayDriver,
102
+ gateway: { services: [{ name: 'users', url: 'http://user-service/graphql' }, { name: 'posts', url: 'http://post-service/graphql' }] },
103
+ });
104
+ ```
105
+
106
+ ## Subscriptions and hiding operations
107
+
108
+ - Subgraphs support subscriptions (`subscriptions` for Apollo, `subscription: true` for Mercurius), but `ApolloGatewayDriver` doesn't forward them: use Apollo Router with federated subscriptions, or the Mercurius gateway (`subscription: true` and `wsUrl` per service).
109
+ - Keep a field/operation out of the public supergraph with Federation 2 `@inaccessible`: `@Directive('@inaccessible')`. It stays executable on the subgraph.
110
+ - Generate SDL offline for federation with `GraphQLSchemaFactory`, passing entity stubs in `orphanedTypes`.
@@ -0,0 +1,133 @@
1
+ # GraphQL schema features
2
+
3
+ ## Contents
4
+ - Schema first
5
+ - Scalars
6
+ - Directives
7
+ - Interfaces
8
+ - Unions and enums
9
+ - Field middleware and extensions
10
+ - Mapped types and inheritance
11
+ - Complexity plugin and other Apollo plugins
12
+ - CLI plugin
13
+ - Generating SDL without running the app
14
+ - Sharing models with the browser
15
+ - Multiple endpoints
16
+ - Mercurius
17
+
18
+ ## Schema first
19
+
20
+ ```typescript
21
+ GraphQLModule.forRoot<ApolloDriverConfig>({
22
+ driver: ApolloDriver,
23
+ typePaths: ['./**/*.graphql'],
24
+ definitions: { path: join(process.cwd(), 'src/graphql.ts'), outputAs: 'class' }, // generated at startup
25
+ });
26
+ ```
27
+
28
+ On-demand typings: a `generate-typings.ts` script with `new GraphQLDefinitionsFactory().generate({ typePaths, path, outputAs: 'class', watch: true })`. Resolver classes use `@Resolver('Author')`, `@Query()`, `@ResolveField()` matching SDL names; interfaces/unions need a `__resolveType` resolver. Add a `ValidationPipe` for argument validation.
29
+
30
+ ## Scalars
31
+
32
+ - Built-ins: `ID`, `String`, `Boolean`, `Int`, `Float`, `GraphQLISODateTime` (default for `Date`), `GraphQLTimestamp`.
33
+ - `buildSchemaOptions: { dateScalarMode: 'timestamp', numberScalarMode: 'integer' }`.
34
+ - Code-first custom scalar class: `@Scalar('Date', () => Date) class DateScalar implements CustomScalar<number, Date> { parseValue, serialize, parseLiteral }` registered as a provider.
35
+ - Imported scalars (e.g. `graphql-type-json`): `resolvers: { JSON: GraphQLJSON }` and `@Field(() => GraphQLJSON)`.
36
+ - `new GraphQLScalarType({ name: 'UUID', serialize, parseValue, parseLiteral })` + `resolvers: { UUID: CustomUuidScalar }`.
37
+
38
+ ## Directives
39
+
40
+ ```typescript
41
+ @Directive('@upper') @Field() title: string; // also @Directive('@deprecated(reason: "…")')
42
+
43
+ GraphQLModule.forRoot({
44
+ transformSchema: (schema) => upperDirectiveTransformer(schema, 'upper'), // mapSchema/getDirective from @graphql-tools/utils
45
+ buildSchemaOptions: { directives: [new GraphQLDirective({ name: 'upper', locations: [DirectiveLocation.FIELD_DEFINITION] })] },
46
+ });
47
+ ```
48
+
49
+ Directives added with `@Directive()` aren't reflected in the generated schema file.
50
+
51
+ ## Interfaces
52
+
53
+ - `@InterfaceType() abstract class Character { @Field(() => ID) id: string }` (TypeScript interfaces can't be used).
54
+ - `@ObjectType({ implements: () => [Character] }) class Human implements Character`.
55
+ - Default `resolveType` needs class instances; otherwise `@InterfaceType({ resolveType(value) { return value.pages ? Book : Author; } })`.
56
+ - Shared field resolvers: `@Resolver(() => Character)` class with `@ResolveField` + `inheritResolversFromInterfaces: true`.
57
+
58
+ ## Unions and enums
59
+
60
+ ```typescript
61
+ export const ResultUnion = createUnionType({ name: 'ResultUnion', types: () => [Author, Book] as const, resolveType(v) { … } }); // `as const` required
62
+ @Query(() => [ResultUnion]) search(): Array<typeof ResultUnion> { … }
63
+
64
+ export enum AllowedColor { RED, GREEN }
65
+ registerEnumType(AllowedColor, { name: 'AllowedColor', description: '…', valuesMap: { RED: { deprecationReason: '…' } } });
66
+ ```
67
+
68
+ ## Field middleware and extensions
69
+
70
+ ```typescript
71
+ const loggerMiddleware: FieldMiddleware = async (ctx: MiddlewareContext, next: NextFn) => { const value = await next(); return value; };
72
+ @Field({ middleware: [loggerMiddleware] }) title: string;
73
+ GraphQLModule.forRoot({ buildSchemaOptions: { fieldMiddleware: [loggerMiddleware] } }); // global, runs before local
74
+ ```
75
+
76
+ - Code first only; no DI; keep them cheap; only on `ObjectType` classes. With `fieldResolverEnhancers`, interceptors/guards also wrap them.
77
+ - Metadata: `@Extensions({ role: Role.ADMIN })` on fields/classes/methods; read in middleware via `ctx.info.parentType.getFields()[ctx.info.fieldName].extensions` (field-level permissions).
78
+
79
+ ## Mapped types and inheritance
80
+
81
+ `PartialType`, `PickType`, `OmitType`, `IntersectionType` from `@nestjs/graphql`, composable; pass the target decorator as the 2nd arg (`PartialType(CreateUserInput, InputType)`) when deriving between kinds. Abstract base classes with `@ObjectType({ isAbstract: true })` / generic resolvers via a factory function returning a class. Custom mapped helpers: treat returned field metadata as read-only.
82
+
83
+ ## Complexity plugin and other Apollo plugins
84
+
85
+ ```typescript
86
+ @Plugin()
87
+ export class ComplexityPlugin implements ApolloServerPlugin {
88
+ constructor(private gqlSchemaHost: GraphQLSchemaHost) {}
89
+ async requestDidStart(): Promise<GraphQLRequestListener<BaseContext>> {
90
+ const { schema } = this.gqlSchemaHost;
91
+ return {
92
+ async didResolveOperation({ request, document }) {
93
+ const complexity = getComplexity({ schema, operationName: request.operationName, query: document, variables: request.variables,
94
+ estimators: [fieldExtensionsEstimator(), simpleEstimator({ defaultComplexity: 1 })] });
95
+ if (complexity > 20) throw new GraphQLError(`Query is too complex: ${complexity}`);
96
+ },
97
+ };
98
+ }
99
+ }
100
+ ```
101
+
102
+ `npm i graphql-query-complexity`; register as a provider; `@Field({ complexity: 3 })` or an estimator function; `@Query(() => T, { complexity: … })`. External plugins: `plugins: [ApolloServerPluginCacheControl({ defaultMaxAge: 5 })]`. Mercurius: register Fastify plugins via `plugins: [{ plugin, options }]` (mercurius-upload in `main.ts`).
103
+
104
+ ## CLI plugin
105
+
106
+ `nest-cli.json`: `"plugins": [{ "name": "@nestjs/graphql", "options": { "typeFileNameSuffix": [".input.ts", ".args.ts", ".entity.ts", ".model.ts"], "introspectComments": true } }]`. Infers `@Field()` from types/optionality; override with explicit `@Field()`. SWC needs `--type-check`; ts-jest needs the `@nestjs/graphql/plugin` AST transformer (same pattern as the Swagger plugin).
107
+
108
+ ## Generating SDL without running the app
109
+
110
+ ```typescript
111
+ const app = await NestFactory.create(GraphQLSchemaBuilderModule);
112
+ await app.init();
113
+ const schema = await app.get(GraphQLSchemaFactory).create([RecipesResolver], [DateScalar], { skipCheck: false, orphanedTypes: [] });
114
+ console.log(printSchema(schema));
115
+ ```
116
+
117
+ `GraphQLSchemaHost#schema` gives the built schema after `onModuleInit`. Federation entities only referenced through `_entities` go into `orphanedTypes`.
118
+
119
+ ## Sharing models with the browser
120
+
121
+ `@nestjs/graphql` exposes a decorator shim via the `browser` export condition; bundlers without it can alias `@nestjs/graphql` to `node_modules/@nestjs/graphql/dist/extra/graphql-model-shim`.
122
+
123
+ ## Multiple endpoints
124
+
125
+ `GraphQLModule.forRoot({ path: '/cats', include: [CatsModule] })` per endpoint. In code first, `include` limits resolvers; types are global unless `@ObjectType({ registerIn: () => CatsModule })` (also on `@InputType`, `@InterfaceType`, `@ArgsType`, `registerEnumType`, `createUnionType`). Prefer the factory form to avoid circular references.
126
+
127
+ ## Mercurius
128
+
129
+ ```typescript
130
+ GraphQLModule.forRoot<MercuriusDriverConfig>({ driver: MercuriusDriver, graphiql: true, subscription: true }); // Fastify only
131
+ ```
132
+
133
+ GraphiQL at `/graphiql`. Subscriptions: `PubSub` from `mercurius` (`mqemitter`; use `mqemitter-redis` in production), auth via `subscription.onConnect`. Async config with `forRootAsync`. Access the schema with `GraphQLSchemaHost`.
@@ -0,0 +1,114 @@
1
+ ---
2
+ name: nestjs-health-checks
3
+ description: Adds liveness and readiness health checks to NestJS 12 with @nestjs/terminus — TerminusModule.forRoot options, HealthCheckService with @HealthCheck() controllers, built-in indicators (HTTP ping/response checks, TypeORM, Mongoose, Sequelize, MikroORM, Prisma, microservice, gRPC, memory heap/RSS, disk), custom indicators with HealthIndicatorService attempt()/up()/down()/degraded(), withTimeout() and cacheFor() on attempts, the shutting_down status and gracefulShutdownTimeoutMs for zero-downtime Kubernetes rollouts, and custom Terminus logging. Use when adding /health, /health/live or /health/ready endpoints, Kubernetes probes, dependency checks for databases or upstream APIs, or when pods are killed or kept in rotation wrongly. Not for metrics, tracing or alerting (nestjs-observability) or retries/circuit breakers (nestjs-resilience).
4
+ compatibility: NestJS 12.x; @nestjs/terminus; @nestjs/axios for HttpHealthIndicator
5
+ metadata:
6
+ version: "0.1.0"
7
+ nestjs-major: "12"
8
+ docs-crawled: "2026-11-05"
9
+ ---
10
+
11
+ # NestJS health checks (@nestjs/terminus)
12
+
13
+ ```bash
14
+ npm i @nestjs/terminus
15
+ npm i @nestjs/axios axios # only for HttpHealthIndicator
16
+ nest g module health && nest g controller health
17
+ ```
18
+
19
+ ## Liveness vs readiness
20
+
21
+ | Probe | Checks | Failing means |
22
+ | --- | --- | --- |
23
+ | Liveness `/health/live` | The process works (event loop answers; optionally heap/RSS limits) | Restart the container — so **no dependency checks** here |
24
+ | Readiness `/health/ready` | Dependencies it needs to serve (DB, required upstreams) | Stop routing traffic; also reports `shutting_down` during shutdown |
25
+
26
+ ## Setup
27
+
28
+ ```typescript
29
+ @Module({
30
+ imports: [TerminusModule.forRoot({ gracefulShutdownTimeoutMs: 5000, errorLogStyle: 'json' }), HttpModule],
31
+ controllers: [HealthController],
32
+ providers: [CacheHealthIndicator],
33
+ })
34
+ export class HealthModule {}
35
+
36
+ @Controller('health')
37
+ export class HealthController {
38
+ constructor(
39
+ private readonly health: HealthCheckService,
40
+ private readonly db: TypeOrmHealthIndicator,
41
+ private readonly memory: MemoryHealthIndicator,
42
+ private readonly cacheIndicator: CacheHealthIndicator,
43
+ ) {}
44
+
45
+ @Get('live') @HealthCheck()
46
+ live() { return this.health.check([() => this.memory.checkHeap('memory_heap', 300 * 1024 * 1024)]); }
47
+
48
+ @Get('ready') @HealthCheck()
49
+ ready() {
50
+ return this.health.check([
51
+ () => this.db.pingCheck('database').withTimeout(1500).cacheFor(5000),
52
+ () => this.cacheIndicator.isHealthy('cache'),
53
+ ]);
54
+ }
55
+ }
56
+ ```
57
+
58
+ - Enable `app.enableShutdownHooks()` — required for the `shutting_down` status and the graceful delay.
59
+ - Make health routes public and unthrottled: `@Public()` (nestjs-authentication / request-pipeline), `@SkipThrottle()`, exclude from the global prefix/versioning if your probes expect `/health` (`setGlobalPrefix('api', { exclude: ['health/(.*)'] })` or `VERSION_NEUTRAL`).
60
+ - Response: `{ status: 'ok' | 'error' | 'degraded' | 'shutting_down', info, error, details }` (`HealthCheckResult`); failures return **503**.
61
+
62
+ ## Built-in indicators
63
+
64
+ - HTTP: `http.pingCheck('docs', url)`, `http.responseCheck('svc', url, (res) => res.status === 204)`.
65
+ - Databases: `TypeOrmHealthIndicator`, `MongooseHealthIndicator`, `SequelizeHealthIndicator`, `MikroOrmHealthIndicator`, `PrismaHealthIndicator` (`pingCheck(key, …)`). Multiple TypeORM data sources: inject each and pass `{ connection }`.
66
+ - `MicroserviceHealthIndicator`, `GRPCHealthIndicator`.
67
+ - `memory.checkHeap(key, bytes)`, `memory.checkRSS(key, bytes)`, `disk.checkStorage(key, { path: '/', thresholdPercent: 0.9 })` or `{ threshold: bytes }`.
68
+ - Use `.withTimeout(ms)` (the `timeout` option is deprecated) and `.cacheFor(ms)` for expensive checks; cached results carry `cachedResponse: true`.
69
+
70
+ ## Custom indicators
71
+
72
+ ```typescript
73
+ @Injectable()
74
+ export class PaymentsApiHealthIndicator {
75
+ constructor(private readonly indicators: HealthIndicatorService, private readonly http: HttpClient) {}
76
+ isHealthy(key: string) {
77
+ return this.indicators.check(key).attempt(async ({ signal }) => { // up on resolve, down on throw
78
+ const { data } = await this.http.get<{ version: string }>('/status', { signal, retry: false });
79
+ return { version: data.version }; // appended to the result
80
+ }).withTimeout(1000);
81
+ }
82
+ }
83
+
84
+ @Injectable()
85
+ export class CacheHealthIndicator {
86
+ constructor(private readonly indicators: HealthIndicatorService, private readonly cache: CacheService) {}
87
+ async isHealthy(key: string) {
88
+ const indicator = this.indicators.check(key);
89
+ return (await this.cache.isConnected()) ? indicator.up() : indicator.degraded('serving without cache'); // degraded ≠ fail
90
+ }
91
+ }
92
+ ```
93
+
94
+ - Pass the `signal` to the operation so timeouts cancel it.
95
+ - With `up()/down()/degraded()`, return the state — a thrown error is treated as a bug (500 instead of 503); wrap risky code in try/catch and return `down()`.
96
+ - Any `down` → overall `error`; `degraded` only if none are down.
97
+
98
+ ## Zero-downtime shutdown
99
+
100
+ `gracefulShutdownTimeoutMs` (applied on SIGTERM only): readiness answers 503 `shutting_down` while the app keeps serving for the delay — set it slightly longer than the readiness probe period, and keep the pod's termination grace period longer than delay + drain time.
101
+
102
+ ## Logging
103
+
104
+ `TerminusModule.forRoot({ logger: TerminusLogger })` (a transient `ConsoleLogger` subclass), `logger: false` to silence, `errorLogStyle: 'pretty' | 'json'`.
105
+
106
+ ## Workflow
107
+
108
+ - [ ] 1. Install; `HealthModule` with `TerminusModule.forRoot()`; shutdown hooks on.
109
+ - [ ] 2. Separate live and ready routes; public, unthrottled, outside prefix/versioning as probes require.
110
+ - [ ] 3. Add indicators for required dependencies with timeouts (and caching); degraded for optional ones.
111
+ - [ ] 4. E2E test: 200 when healthy, 503 when an indicator is overridden to fail.
112
+ - [ ] 5. Build, lint, run tests, start the app and curl both endpoints; wire the probes in the deployment manifest.
113
+
114
+ Sources: [Health checks (Terminus)](https://docs.nestjs.com/reliability/terminus)
@@ -0,0 +1,118 @@
1
+ ---
2
+ name: nestjs-http-client
3
+ description: Calls external HTTP APIs from NestJS 12 with the first-party fetch-based @nestjs/http-client — HttpClientModule.register/registerAsync named clients with baseUrl, headers and timeouts, forRoot app-wide defaults, typed get/post/put/patch/delete with params, query, json and responseType, per-attempt timeouts and AbortSignal deadlines, retries on by default for idempotent methods with backoff and Retry-After, idempotency keys for POST, HttpResponseError/HttpTimeoutError/HttpNetworkError handling and toHttpException 502/504 mapping, function and class interceptors, streaming responses, undici dispatchers, testing with a stub fetch, and migrating from @nestjs/axios. Use when a service must call another service or third-party API, when adding auth headers, retries or timeouts to outbound calls, or when replacing HttpModule/HttpService (Axios). Not for receiving webhooks (nestjs-webhooks), circuit breakers and bulkheads around calls (nestjs-resilience) or microservice transports (nestjs-microservices).
4
+ compatibility: NestJS 12.x; @nestjs/http-client (no runtime deps); optional undici for custom dispatchers
5
+ metadata:
6
+ version: "0.1.0"
7
+ nestjs-major: "12"
8
+ docs-crawled: "2026-11-05"
9
+ ---
10
+
11
+ # NestJS HTTP client (@nestjs/http-client)
12
+
13
+ Prefer `@nestjs/http-client` for new outbound calls; it replaces `@nestjs/axios`'s `HttpService`. If the project uses `@nestjs/axios`, keep it unless asked to migrate (map in `references/http-client-reference.md`). Every method returns a Promise — no `firstValueFrom`.
14
+
15
+ ```bash
16
+ npm i @nestjs/http-client
17
+ ```
18
+
19
+ ## Register clients
20
+
21
+ ```typescript
22
+ // app.module.ts — shared defaults (always global; no baseUrl here)
23
+ HttpClientModule.forRoot({ headers: { 'user-agent': 'orders-service/1.0' }, timeout: '10s' }),
24
+
25
+ // github/github.module.ts — one named client per upstream
26
+ HttpClientModule.registerAsync({
27
+ name: 'github',
28
+ imports: [ConfigModule],
29
+ inject: [ConfigService],
30
+ useFactory: (config: ConfigService) => ({
31
+ baseUrl: config.getOrThrow<string>('GITHUB_API_URL'),
32
+ headers: { authorization: `Bearer ${config.getOrThrow<string>('GITHUB_TOKEN')}` },
33
+ timeout: '5s',
34
+ }),
35
+ interceptors: [GithubAuthInterceptor], // classes go beside useFactory
36
+ }),
37
+ ```
38
+
39
+ - `register()` scopes the client to the importing module (`isGlobal: true` to share). Unnamed = default client injected as `HttpClient`; named clients with `@InjectHttpClient('github')`; token `getHttpClientToken(name)`.
40
+ - Client settings override `forRoot()` defaults, except headers (merged; `null` removes), retry (merged per field) and interceptors (forRoot's run first).
41
+
42
+ ## Make requests
43
+
44
+ ```typescript
45
+ @Injectable()
46
+ export class ReposService {
47
+ constructor(@InjectHttpClient('github') private readonly github: HttpClient) {}
48
+
49
+ async findAll(org: string) {
50
+ const { data } = await this.github.get<Repo[]>('/orgs/:org/repos', {
51
+ params: { org }, // fills :org, URI-encoded; '..' or missing throws
52
+ query: { type: 'public', topic: ['nest', 'ts'] }, // arrays repeat the key
53
+ });
54
+ return data; // type arg is NOT validated at runtime
55
+ }
56
+
57
+ async createIssue(owner: string, repo: string, issue: CreateIssueDto) {
58
+ const { data } = await this.github.post<Issue>('/repos/:owner/:repo/issues', { params: { owner, repo }, json: issue });
59
+ return data;
60
+ }
61
+ }
62
+ ```
63
+
64
+ - Relative URLs append to `baseUrl` with one `/` (the `/v1` prefix is kept). Never interpolate user input into paths — use `params`/`query`.
65
+ - `json` (sets content-type) or `body` (string, Buffer, FormData, stream…), not both. `responseType`: `'auto'` (default), `'json'`, `'text'`, `'arrayBuffer'`, `'stream'`, `'response'`.
66
+ - Response: `{ status, statusText, ok, headers (web Headers: .get()), data, url, request }`.
67
+ - Validate responses from APIs you don't control (e.g. a Standard Schema `parse`).
68
+
69
+ ## Timeouts, retries, idempotency
70
+
71
+ - `timeout` applies **per attempt**; a call can take `attempts × timeout` + waits. Cap the whole call with `signal: AbortSignal.timeout(10_000)`.
72
+ - Retries are **on by default**: 3 attempts for GET/HEAD/OPTIONS/PUT/DELETE on network errors, timeouts and 408/429/500/502/503/504, exponential backoff with full jitter (honours `Retry-After`). Configure with `retry: { attempts, backoff: { delay, factor, maxDelay, jitter }, retryIf, methods, statusCodes }` or `retry: false`; `methods`/`statusCodes` **replace** the defaults.
73
+ - POST/PATCH aren't retried. With an upstream that supports idempotency keys: `headers: { 'idempotency-key': \`charge-${order.id}\` }, retry: { methods: ['POST'] }` on that request only.
74
+ - Retry at one layer of a service chain only (retries multiply).
75
+
76
+ ## Errors
77
+
78
+ ```typescript
79
+ try {
80
+ return (await this.github.get<Repo>('/repos/:owner/:repo', { params: { owner, repo } })).data;
81
+ } catch (error) {
82
+ if (error instanceof HttpResponseError && error.status === 404) throw new NotFoundException();
83
+ throw toHttpException(error); // upstream non-2xx/network/parse → 502, timeouts → 504 (logged)
84
+ }
85
+ ```
86
+
87
+ - Errors extend `HttpClientError`: `HttpResponseError` (`status`, `body`), `HttpTimeoutError`, `HttpNetworkError` (`cause.code`), `HttpParseError`. `throwOnHttpError: false` returns non-2xx as values.
88
+ - Or map centrally: `@Catch(HttpClientError, DOMException) class F extends BaseExceptionFilter { catch(e, h) { super.catch(toHttpException(e), h); } }`.
89
+ - `toHttpException(error, { forward: [422] })` passes selected upstream statuses/bodies through; `log: false` if you log elsewhere. In RPC/WS handlers convert to `RpcException`/`WsException` yourself.
90
+
91
+ ## Security defaults
92
+
93
+ A client with `baseUrl` only talks to its origin (other hosts and cross-origin redirects throw), credentials in URLs are refused, and error messages mask query values. Keep `redirect` default unless the API intentionally redirects off-origin (`'follow'`).
94
+
95
+ ## Testing
96
+
97
+ ```typescript
98
+ const fetch = vi.fn<typeof globalThis.fetch>();
99
+ const moduleRef = await Test.createTestingModule({ imports: [HttpClientModule.forRoot({ fetch }), ReposModule] }).compile();
100
+ await moduleRef.init();
101
+ fetch.mockResolvedValueOnce(Response.json([{ name: 'nest' }])); // one Response per call (bodies read once)
102
+ ```
103
+
104
+ E2E: `.overrideProvider(HTTP_CLIENT_MODULE_OPTIONS).useValue({ fetch })`, or replace a client via `getHttpClientToken('github')`. Test retries with `retry: false` or several queued responses.
105
+
106
+ ## Workflow
107
+
108
+ - [ ] 1. Install; `forRoot()` defaults; one named client per upstream with `registerAsync` + `ConfigService`.
109
+ - [ ] 2. Service methods with `params`/`query`/`json`; validate responses.
110
+ - [ ] 3. Set timeouts, a deadline for user-facing calls, retry policy and idempotency keys for unsafe methods.
111
+ - [ ] 4. Map errors with `toHttpException` or a filter; add a logging interceptor if no tracing.
112
+ - [ ] 5. Stub-fetch tests. Build, lint, run tests, start the app.
113
+
114
+ ## Reference files
115
+
116
+ - `references/http-client-reference.md`: interceptors (function and class, per-attempt), streaming to `StreamableFile`, undici `Agent`/`ProxyAgent` dispatchers, redirect modes, RxJS wrapping with cancellation, and the `@nestjs/axios` migration table. Read when going beyond the basics or migrating.
117
+
118
+ Sources: [HTTP client](https://docs.nestjs.com/application/http-client)
@@ -0,0 +1,116 @@
1
+ # @nestjs/http-client reference
2
+
3
+ ## Contents
4
+ - Interceptors
5
+ - Streaming responses
6
+ - Dispatchers: pools, proxies, mTLS
7
+ - Redirects
8
+ - Async configuration variants
9
+ - RxJS
10
+ - Migrating from @nestjs/axios
11
+
12
+ ## Interceptors
13
+
14
+ Function form:
15
+
16
+ ```typescript
17
+ export function apiKeyInterceptor(apiKey: string): HttpClientInterceptorFn {
18
+ return (request, next) => { request.headers.set('x-api-key', apiKey); return next(request); };
19
+ }
20
+ ```
21
+
22
+ Class form (DI):
23
+
24
+ ```typescript
25
+ @Injectable()
26
+ export class GithubAuthInterceptor implements HttpClientInterceptor {
27
+ constructor(private readonly tokens: GithubTokenService) {}
28
+ async intercept(request: HttpRequest, next: HttpHandler): Promise<Response> {
29
+ request.headers.set('authorization', `Bearer ${await this.tokens.getToken()}`);
30
+ return next(request);
31
+ }
32
+ }
33
+ ```
34
+
35
+ - `HttpRequest`: `method`, `url` (URL), `headers` (Headers), `body`, `signal`, `attempt`, `context` (from the request's `context` option).
36
+ - Listed first = outermost; `forRoot()` interceptors run before the client's. They run **once per attempt** (fresh tokens on retry). They see the raw `Response` before the client throws for non-2xx.
37
+ - Class interceptors resolve at app init; an existing provider instance is reused.
38
+ - Logging interceptor: log `method origin+pathname status ms attempt` — not the query string (PII/keys). Tracing needs no interceptor with `@nestjs/observe`.
39
+
40
+ ## Streaming responses
41
+
42
+ ```typescript
43
+ @Get(':id/pdf')
44
+ async download(@Param('id') id: string) {
45
+ const { data, headers } = await this.billing.get('/invoices/:id/pdf', { params: { id }, responseType: 'stream' });
46
+ return new StreamableFile(data, { type: headers.get('content-type') ?? 'application/pdf' });
47
+ }
48
+ ```
49
+
50
+ Timeout stops once headers arrive; the `signal` still cancels. Don't forward upstream `content-length` (fetch decompresses).
51
+
52
+ ## Dispatchers: pools, proxies, mTLS
53
+
54
+ ```typescript
55
+ import { Agent, ProxyAgent } from 'undici'; // install undici yourself, matching your Node's bundled major
56
+
57
+ HttpClientModule.register({
58
+ name: 'partner',
59
+ baseUrl: 'https://partner.example.com',
60
+ dispatcher: new Agent({ connections: 20, connect: { ca, cert, key } }),
61
+ });
62
+ // proxy: dispatcher: new ProxyAgent('http://proxy.internal:3128')
63
+ ```
64
+
65
+ Node `http.Agent` instances don't work with fetch.
66
+
67
+ ## Redirects
68
+
69
+ Default with `baseUrl`: follow same-origin only, cross-origin → `HttpNetworkError`. `'follow'` anywhere, `'error'` on any redirect, `'manual'` returns 3xx (combine with `throwOnHttpError: false`).
70
+
71
+ ## Async configuration variants
72
+
73
+ - `registerAsync({ name, imports, inject, useFactory, interceptors, isGlobal })` — factory returns `HttpClientFactoryOptions` (no name/isGlobal/imports/interceptor classes).
74
+ - `useClass: GithubClientConfig` implementing `HttpClientOptionsFactory.createHttpClientOptions()`; `useExisting` with the exporting module in `imports`.
75
+ - `forRootAsync()` with `HttpClientModuleOptionsFactory`.
76
+ - Outside Nest (scripts): `new HttpClient(options)`.
77
+
78
+ ## RxJS
79
+
80
+ `defer(() => this.http.get('/cats'))` starts on subscribe; neither `defer` nor `from` cancels on unsubscribe. For cancellation:
81
+
82
+ ```typescript
83
+ function fromRequest<T>(send: (signal: AbortSignal) => Promise<T>): Observable<T> {
84
+ return new Observable<T>((subscriber) => {
85
+ const controller = new AbortController();
86
+ send(controller.signal).then((v) => { subscriber.next(v); subscriber.complete(); }, (e) => subscriber.error(e));
87
+ return () => controller.abort();
88
+ });
89
+ }
90
+ ```
91
+
92
+ ## Migrating from @nestjs/axios
93
+
94
+ | `@nestjs/axios` | `@nestjs/http-client` |
95
+ | --- | --- |
96
+ | `HttpModule.register({ baseURL })` | `HttpClientModule.register({ baseUrl })` |
97
+ | `HttpModule.registerAsync()` | `HttpClientModule.registerAsync()` (also takes `name`) |
98
+ | `HttpModuleOptionsFactory.createHttpOptions()` | `HttpClientOptionsFactory.createHttpClientOptions()` |
99
+ | `extraProviders` | a module exporting the providers, in `imports` |
100
+ | one `HttpService` per module | named clients, `@InjectHttpClient(name)` |
101
+ | Observables + `firstValueFrom(...)` | `await this.http.get(url)` |
102
+ | `post(url, data)` | `post(url, { json: data })` |
103
+ | `params` (query) | `query` (arrays repeat keys); `params` = path segments |
104
+ | `response.headers['etag']` | `response.headers.get('etag')` |
105
+ | `AxiosError` / `error.response?.status` | `HttpResponseError` / `error.status`, `error.body` |
106
+ | `error.code === 'ECONNABORTED'` | `HttpTimeoutError` |
107
+ | `validateStatus: () => true` | `throwOnHttpError: false` |
108
+ | `axiosRef` | none; options or a custom `fetch` |
109
+ | `axiosRef.interceptors.*.use()` | `interceptors` (one function for request + response) |
110
+ | `httpsAgent` / `proxy` | `dispatcher` (undici `Agent` / `ProxyAgent`) |
111
+ | `maxRedirects: 0` | `redirect: 'manual'` |
112
+ | no retries | retries on by default; `retry: false` to match old behaviour |
113
+ | `timeout` whole request | `timeout` per attempt (`'5s'`) |
114
+ | `onUploadProgress` / `onDownloadProgress` | none; count stream bytes |
115
+
116
+ Both packages can coexist during migration. Update tests: mock `fetch` instead of `HttpService`.