@biggora/claude-plugins 1.2.2 → 1.3.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.
- package/README.md +2 -0
- package/package.json +1 -1
- package/registry/registry.json +15 -0
- package/specs/coding.md +6 -0
- package/src/commands/skills/add.js +63 -7
- package/src/commands/skills/list.js +23 -52
- package/src/commands/skills/remove.js +26 -27
- package/src/commands/skills/resolve.js +155 -0
- package/src/commands/skills/update.js +58 -74
- package/src/skills/nest-best-practices/SKILL.md +251 -0
- package/src/skills/nest-best-practices/references/best-practices-request-lifecycle.md +158 -0
- package/src/skills/nest-best-practices/references/cli-monorepo.md +106 -0
- package/src/skills/nest-best-practices/references/cli-overview.md +157 -0
- package/src/skills/nest-best-practices/references/core-controllers.md +165 -0
- package/src/skills/nest-best-practices/references/core-dependency-injection.md +179 -0
- package/src/skills/nest-best-practices/references/core-middleware.md +139 -0
- package/src/skills/nest-best-practices/references/core-modules.md +138 -0
- package/src/skills/nest-best-practices/references/core-providers.md +188 -0
- package/src/skills/nest-best-practices/references/faq-raw-body-hybrid.md +122 -0
- package/src/skills/nest-best-practices/references/fundamentals-circular-dependency.md +89 -0
- package/src/skills/nest-best-practices/references/fundamentals-custom-decorators.md +107 -0
- package/src/skills/nest-best-practices/references/fundamentals-dynamic-modules.md +125 -0
- package/src/skills/nest-best-practices/references/fundamentals-exception-filters.md +202 -0
- package/src/skills/nest-best-practices/references/fundamentals-execution-context.md +107 -0
- package/src/skills/nest-best-practices/references/fundamentals-guards.md +136 -0
- package/src/skills/nest-best-practices/references/fundamentals-interceptors.md +187 -0
- package/src/skills/nest-best-practices/references/fundamentals-lazy-loading.md +89 -0
- package/src/skills/nest-best-practices/references/fundamentals-lifecycle-events.md +87 -0
- package/src/skills/nest-best-practices/references/fundamentals-module-reference.md +107 -0
- package/src/skills/nest-best-practices/references/fundamentals-pipes.md +197 -0
- package/src/skills/nest-best-practices/references/fundamentals-provider-scopes.md +92 -0
- package/src/skills/nest-best-practices/references/fundamentals-testing.md +142 -0
- package/src/skills/nest-best-practices/references/graphql-overview.md +233 -0
- package/src/skills/nest-best-practices/references/graphql-resolvers-mutations.md +199 -0
- package/src/skills/nest-best-practices/references/graphql-scalars-unions-enums.md +180 -0
- package/src/skills/nest-best-practices/references/graphql-subscriptions.md +228 -0
- package/src/skills/nest-best-practices/references/microservices-grpc.md +175 -0
- package/src/skills/nest-best-practices/references/microservices-overview.md +221 -0
- package/src/skills/nest-best-practices/references/microservices-transports.md +119 -0
- package/src/skills/nest-best-practices/references/openapi-swagger.md +207 -0
- package/src/skills/nest-best-practices/references/recipes-authentication.md +97 -0
- package/src/skills/nest-best-practices/references/recipes-cqrs.md +176 -0
- package/src/skills/nest-best-practices/references/recipes-crud-generator.md +87 -0
- package/src/skills/nest-best-practices/references/recipes-documentation.md +93 -0
- package/src/skills/nest-best-practices/references/recipes-mongoose.md +153 -0
- package/src/skills/nest-best-practices/references/recipes-prisma.md +98 -0
- package/src/skills/nest-best-practices/references/recipes-terminus.md +148 -0
- package/src/skills/nest-best-practices/references/recipes-typeorm.md +122 -0
- package/src/skills/nest-best-practices/references/security-authorization.md +196 -0
- package/src/skills/nest-best-practices/references/security-cors-helmet-rate-limiting.md +204 -0
- package/src/skills/nest-best-practices/references/security-encryption-hashing.md +93 -0
- package/src/skills/nest-best-practices/references/techniques-caching.md +142 -0
- package/src/skills/nest-best-practices/references/techniques-compression-streaming-sse.md +194 -0
- package/src/skills/nest-best-practices/references/techniques-configuration.md +132 -0
- package/src/skills/nest-best-practices/references/techniques-database.md +153 -0
- package/src/skills/nest-best-practices/references/techniques-events.md +163 -0
- package/src/skills/nest-best-practices/references/techniques-fastify.md +137 -0
- package/src/skills/nest-best-practices/references/techniques-file-upload.md +140 -0
- package/src/skills/nest-best-practices/references/techniques-http-module.md +176 -0
- package/src/skills/nest-best-practices/references/techniques-logging.md +146 -0
- package/src/skills/nest-best-practices/references/techniques-mvc-serve-static.md +132 -0
- package/src/skills/nest-best-practices/references/techniques-queues.md +162 -0
- package/src/skills/nest-best-practices/references/techniques-serialization.md +158 -0
- package/src/skills/nest-best-practices/references/techniques-sessions-cookies.md +167 -0
- package/src/skills/nest-best-practices/references/techniques-task-scheduling.md +166 -0
- package/src/skills/nest-best-practices/references/techniques-validation.md +126 -0
- package/src/skills/nest-best-practices/references/techniques-versioning.md +153 -0
- package/src/skills/nest-best-practices/references/websockets-advanced.md +96 -0
- package/src/skills/nest-best-practices/references/websockets-gateways.md +215 -0
- package/src/skills/typescript-expert/SKILL.md +145 -0
- package/src/skills/typescript-expert/commands/typescript-fix.md +65 -0
- package/src/skills/typescript-expert/references/advanced-conditional-types.md +190 -0
- package/src/skills/typescript-expert/references/advanced-decorators.md +243 -0
- package/src/skills/typescript-expert/references/advanced-mapped-types.md +223 -0
- package/src/skills/typescript-expert/references/advanced-template-literals.md +209 -0
- package/src/skills/typescript-expert/references/advanced-type-guards.md +308 -0
- package/src/skills/typescript-expert/references/best-practices-patterns.md +313 -0
- package/src/skills/typescript-expert/references/best-practices-performance.md +185 -0
- package/src/skills/typescript-expert/references/best-practices-tsconfig.md +242 -0
- package/src/skills/typescript-expert/references/core-generics.md +246 -0
- package/src/skills/typescript-expert/references/core-interfaces-types.md +231 -0
- package/src/skills/typescript-expert/references/core-type-system.md +261 -0
- package/src/skills/typescript-expert/references/core-utility-types.md +235 -0
- package/src/skills/typescript-expert/references/features-ts5x.md +370 -0
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: graphql-subscriptions
|
|
3
|
+
description: Real-time GraphQL subscriptions with PubSub
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# GraphQL Subscriptions
|
|
7
|
+
|
|
8
|
+
Subscriptions enable real-time server-to-client communication.
|
|
9
|
+
|
|
10
|
+
## Enable Subscriptions (Apollo)
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
GraphQLModule.forRoot<ApolloDriverConfig>({
|
|
14
|
+
driver: ApolloDriver,
|
|
15
|
+
subscriptions: {
|
|
16
|
+
'graphql-ws': true,
|
|
17
|
+
},
|
|
18
|
+
}),
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Basic Subscription
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
import { Resolver, Mutation, Subscription } from '@nestjs/graphql';
|
|
25
|
+
import { PubSub } from 'graphql-subscriptions';
|
|
26
|
+
|
|
27
|
+
const pubSub = new PubSub();
|
|
28
|
+
|
|
29
|
+
@Resolver(() => Comment)
|
|
30
|
+
export class CommentsResolver {
|
|
31
|
+
@Subscription(() => Comment)
|
|
32
|
+
commentAdded() {
|
|
33
|
+
return pubSub.asyncIterableIterator('commentAdded');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
@Mutation(() => Comment)
|
|
37
|
+
async addComment(@Args('input') input: CreateCommentInput) {
|
|
38
|
+
const comment = await this.commentsService.create(input);
|
|
39
|
+
|
|
40
|
+
// Publish event
|
|
41
|
+
pubSub.publish('commentAdded', { commentAdded: comment });
|
|
42
|
+
|
|
43
|
+
return comment;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Filtering Events
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
@Subscription(() => Comment, {
|
|
52
|
+
filter: (payload, variables) =>
|
|
53
|
+
payload.commentAdded.postId === variables.postId,
|
|
54
|
+
})
|
|
55
|
+
commentAdded(@Args('postId') postId: string) {
|
|
56
|
+
return pubSub.asyncIterableIterator('commentAdded');
|
|
57
|
+
}
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## Transforming Payload
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
@Subscription(() => Comment, {
|
|
64
|
+
resolve: (payload) => payload.commentAdded,
|
|
65
|
+
})
|
|
66
|
+
commentAdded() {
|
|
67
|
+
return pubSub.asyncIterableIterator('commentAdded');
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Named Subscription
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
@Subscription(() => Comment, {
|
|
75
|
+
name: 'commentAdded',
|
|
76
|
+
})
|
|
77
|
+
subscribeToCommentAdded() {
|
|
78
|
+
return pubSub.asyncIterableIterator('commentAdded');
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## PubSub as Provider
|
|
83
|
+
|
|
84
|
+
Inject PubSub instead of global instance:
|
|
85
|
+
|
|
86
|
+
```typescript
|
|
87
|
+
// pubsub.module.ts
|
|
88
|
+
@Module({
|
|
89
|
+
providers: [
|
|
90
|
+
{
|
|
91
|
+
provide: 'PUB_SUB',
|
|
92
|
+
useValue: new PubSub(),
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
exports: ['PUB_SUB'],
|
|
96
|
+
})
|
|
97
|
+
export class PubSubModule {}
|
|
98
|
+
|
|
99
|
+
// resolver
|
|
100
|
+
@Resolver()
|
|
101
|
+
export class CommentsResolver {
|
|
102
|
+
constructor(@Inject('PUB_SUB') private pubSub: PubSub) {}
|
|
103
|
+
|
|
104
|
+
@Subscription(() => Comment)
|
|
105
|
+
commentAdded() {
|
|
106
|
+
return this.pubSub.asyncIterableIterator('commentAdded');
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Production PubSub
|
|
112
|
+
|
|
113
|
+
The default `PubSub` is not for production. Use Redis-backed implementation:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
npm install graphql-redis-subscriptions ioredis
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
```typescript
|
|
120
|
+
import { RedisPubSub } from 'graphql-redis-subscriptions';
|
|
121
|
+
import Redis from 'ioredis';
|
|
122
|
+
|
|
123
|
+
const options = {
|
|
124
|
+
host: 'localhost',
|
|
125
|
+
port: 6379,
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
{
|
|
129
|
+
provide: 'PUB_SUB',
|
|
130
|
+
useFactory: () => new RedisPubSub({
|
|
131
|
+
publisher: new Redis(options),
|
|
132
|
+
subscriber: new Redis(options),
|
|
133
|
+
}),
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## WebSocket Authentication
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
GraphQLModule.forRoot<ApolloDriverConfig>({
|
|
141
|
+
driver: ApolloDriver,
|
|
142
|
+
subscriptions: {
|
|
143
|
+
'graphql-ws': {
|
|
144
|
+
onConnect: (context) => {
|
|
145
|
+
const { connectionParams, extra } = context;
|
|
146
|
+
const token = connectionParams.authToken;
|
|
147
|
+
|
|
148
|
+
if (!isValidToken(token)) {
|
|
149
|
+
throw new Error('Invalid token');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
extra.user = decodeToken(token);
|
|
153
|
+
},
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
context: ({ extra }) => ({
|
|
157
|
+
user: extra?.user,
|
|
158
|
+
}),
|
|
159
|
+
}),
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## Mercurius Subscriptions
|
|
163
|
+
|
|
164
|
+
```typescript
|
|
165
|
+
GraphQLModule.forRoot<MercuriusDriverConfig>({
|
|
166
|
+
driver: MercuriusDriver,
|
|
167
|
+
subscription: true,
|
|
168
|
+
}),
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
@Subscription(() => Comment)
|
|
173
|
+
commentAdded(@Context('pubsub') pubSub: PubSub) {
|
|
174
|
+
return pubSub.subscribe('commentAdded');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
@Mutation(() => Comment)
|
|
178
|
+
async addComment(
|
|
179
|
+
@Args('input') input: CreateCommentInput,
|
|
180
|
+
@Context('pubsub') pubSub: PubSub,
|
|
181
|
+
) {
|
|
182
|
+
const comment = await this.commentsService.create(input);
|
|
183
|
+
|
|
184
|
+
await pubSub.publish({
|
|
185
|
+
topic: 'commentAdded',
|
|
186
|
+
payload: { commentAdded: comment },
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
return comment;
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
## Client-Side
|
|
194
|
+
|
|
195
|
+
```javascript
|
|
196
|
+
import { createClient } from 'graphql-ws';
|
|
197
|
+
|
|
198
|
+
const client = createClient({
|
|
199
|
+
url: 'ws://localhost:3000/graphql',
|
|
200
|
+
connectionParams: {
|
|
201
|
+
authToken: 'Bearer xxx',
|
|
202
|
+
},
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
client.subscribe(
|
|
206
|
+
{
|
|
207
|
+
query: `subscription { commentAdded { id content } }`,
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
next: (data) => console.log(data),
|
|
211
|
+
error: (error) => console.error(error),
|
|
212
|
+
complete: () => console.log('done'),
|
|
213
|
+
},
|
|
214
|
+
);
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
## Key Points
|
|
218
|
+
|
|
219
|
+
- Use `graphql-ws` instead of deprecated `subscriptions-transport-ws`
|
|
220
|
+
- Payload shape must match subscription return type
|
|
221
|
+
- Use Redis-backed PubSub for production (multi-instance support)
|
|
222
|
+
- `filter` function determines which clients receive events
|
|
223
|
+
- `resolve` function transforms the payload before sending
|
|
224
|
+
|
|
225
|
+
<!--
|
|
226
|
+
Source references:
|
|
227
|
+
- https://docs.nestjs.com/graphql/subscriptions
|
|
228
|
+
-->
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: microservices-grpc
|
|
3
|
+
description: gRPC microservices with Protocol Buffers
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# gRPC Microservices
|
|
7
|
+
|
|
8
|
+
High-performance RPC framework using Protocol Buffers.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm i @grpc/grpc-js @grpc/proto-loader
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Server Setup
|
|
17
|
+
|
|
18
|
+
```typescript
|
|
19
|
+
const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, {
|
|
20
|
+
transport: Transport.GRPC,
|
|
21
|
+
options: {
|
|
22
|
+
package: 'hero',
|
|
23
|
+
protoPath: join(__dirname, 'hero/hero.proto'),
|
|
24
|
+
url: '0.0.0.0:50051',
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Proto Definition
|
|
30
|
+
|
|
31
|
+
```protobuf
|
|
32
|
+
// hero.proto
|
|
33
|
+
syntax = "proto3";
|
|
34
|
+
|
|
35
|
+
package hero;
|
|
36
|
+
|
|
37
|
+
service HeroesService {
|
|
38
|
+
rpc FindOne (HeroById) returns (Hero) {}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
message HeroById {
|
|
42
|
+
int32 id = 1;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
message Hero {
|
|
46
|
+
int32 id = 1;
|
|
47
|
+
string name = 2;
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## nest-cli.json (Copy Proto Files)
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"compilerOptions": {
|
|
56
|
+
"assets": ["**/*.proto"],
|
|
57
|
+
"watchAssets": true
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Controller
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
@Controller()
|
|
66
|
+
export class HeroesController {
|
|
67
|
+
@GrpcMethod('HeroesService', 'FindOne')
|
|
68
|
+
findOne(data: HeroById, metadata: Metadata, call: ServerUnaryCall<any, any>): Hero {
|
|
69
|
+
return this.items.find(({ id }) => id === data.id);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Omit decorator args for auto-binding: `@GrpcMethod()` uses class/method name.
|
|
75
|
+
|
|
76
|
+
## Client
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
ClientsModule.register([
|
|
80
|
+
{
|
|
81
|
+
name: 'HERO_PACKAGE',
|
|
82
|
+
transport: Transport.GRPC,
|
|
83
|
+
options: {
|
|
84
|
+
package: 'hero',
|
|
85
|
+
protoPath: join(__dirname, 'hero/hero.proto'),
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
]),
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
```typescript
|
|
92
|
+
@Injectable()
|
|
93
|
+
export class AppService implements OnModuleInit {
|
|
94
|
+
private heroesService: HeroesService;
|
|
95
|
+
|
|
96
|
+
constructor(@Inject('HERO_PACKAGE') private client: ClientGrpc) {}
|
|
97
|
+
|
|
98
|
+
onModuleInit() {
|
|
99
|
+
this.heroesService = this.client.getService<HeroesService>('HeroesService');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
getHero() {
|
|
103
|
+
return this.heroesService.findOne({ id: 1 });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## gRPC Metadata
|
|
109
|
+
|
|
110
|
+
```typescript
|
|
111
|
+
// Send
|
|
112
|
+
const metadata = new Metadata();
|
|
113
|
+
metadata.add('Set-Cookie', 'yummy_cookie=choco');
|
|
114
|
+
return this.heroesService.findOne({ id: 1 }, metadata);
|
|
115
|
+
|
|
116
|
+
// Receive and respond
|
|
117
|
+
findOne(data, metadata: Metadata, call: ServerUnaryCall<any, any>) {
|
|
118
|
+
const serverMetadata = new Metadata();
|
|
119
|
+
serverMetadata.add('Set-Cookie', 'choco');
|
|
120
|
+
call.sendMetadata(serverMetadata);
|
|
121
|
+
return hero;
|
|
122
|
+
}
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Streaming
|
|
126
|
+
|
|
127
|
+
Use `@GrpcStreamMethod()` for duplex streams:
|
|
128
|
+
|
|
129
|
+
```typescript
|
|
130
|
+
@GrpcStreamMethod()
|
|
131
|
+
bidiHello(messages: Observable<any>, metadata, call): Observable<any> {
|
|
132
|
+
return messages.pipe(
|
|
133
|
+
map((msg) => ({ reply: `Hello, ${msg.greeting}!` })),
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
Or `@GrpcStreamCall()` for raw stream access:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
@GrpcStreamCall()
|
|
142
|
+
bidiHello(requestStream: any) {
|
|
143
|
+
requestStream.on('data', (msg) => {
|
|
144
|
+
requestStream.write({ reply: 'Hello!' });
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## gRPC Reflection
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
npm i @grpc/reflection
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
import { ReflectionService } from '@grpc/reflection';
|
|
157
|
+
|
|
158
|
+
options: {
|
|
159
|
+
onLoadPackageDefinition: (pkg, server) => {
|
|
160
|
+
new ReflectionService(pkg).addToServer(server);
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Key Points
|
|
166
|
+
|
|
167
|
+
- Use `ClientGrpc.getService()` not `ClientProxy`
|
|
168
|
+
- Methods are lowerCamelCase in TypeScript
|
|
169
|
+
- Set `loader.keepCase: true` for underscore field names
|
|
170
|
+
- Streaming handlers must return `Observable` for full-duplex
|
|
171
|
+
|
|
172
|
+
<!--
|
|
173
|
+
Source references:
|
|
174
|
+
- https://docs.nestjs.com/microservices/grpc
|
|
175
|
+
-->
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: microservices
|
|
3
|
+
description: Building microservices with various transport layers
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Microservices
|
|
7
|
+
|
|
8
|
+
NestJS supports microservice architecture with multiple transport protocols.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install @nestjs/microservices
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Available Transporters
|
|
17
|
+
|
|
18
|
+
| Transport | Package |
|
|
19
|
+
|-----------|---------|
|
|
20
|
+
| TCP | Built-in |
|
|
21
|
+
| Redis | `redis` |
|
|
22
|
+
| MQTT | `mqtt` |
|
|
23
|
+
| NATS | `nats` |
|
|
24
|
+
| RabbitMQ | `amqplib` |
|
|
25
|
+
| Kafka | `kafkajs` |
|
|
26
|
+
| gRPC | `@grpc/grpc-js` |
|
|
27
|
+
|
|
28
|
+
## Creating a Microservice
|
|
29
|
+
|
|
30
|
+
```typescript
|
|
31
|
+
// main.ts
|
|
32
|
+
import { NestFactory } from '@nestjs/core';
|
|
33
|
+
import { Transport, MicroserviceOptions } from '@nestjs/microservices';
|
|
34
|
+
import { AppModule } from './app.module';
|
|
35
|
+
|
|
36
|
+
async function bootstrap() {
|
|
37
|
+
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
|
|
38
|
+
AppModule,
|
|
39
|
+
{
|
|
40
|
+
transport: Transport.TCP,
|
|
41
|
+
options: { host: 'localhost', port: 3001 },
|
|
42
|
+
},
|
|
43
|
+
);
|
|
44
|
+
await app.listen();
|
|
45
|
+
}
|
|
46
|
+
bootstrap();
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Hybrid Application
|
|
50
|
+
|
|
51
|
+
Combine HTTP and microservice:
|
|
52
|
+
|
|
53
|
+
```typescript
|
|
54
|
+
const app = await NestFactory.create(AppModule);
|
|
55
|
+
app.connectMicroservice<MicroserviceOptions>({
|
|
56
|
+
transport: Transport.TCP,
|
|
57
|
+
options: { port: 3001 },
|
|
58
|
+
});
|
|
59
|
+
await app.startAllMicroservices();
|
|
60
|
+
await app.listen(3000);
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Request-Response Pattern
|
|
64
|
+
|
|
65
|
+
### Handler (Microservice)
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
import { Controller } from '@nestjs/common';
|
|
69
|
+
import { MessagePattern, Payload } from '@nestjs/microservices';
|
|
70
|
+
|
|
71
|
+
@Controller()
|
|
72
|
+
export class MathController {
|
|
73
|
+
@MessagePattern({ cmd: 'sum' })
|
|
74
|
+
sum(@Payload() data: number[]): number {
|
|
75
|
+
return data.reduce((a, b) => a + b, 0);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Client (Producer)
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import { Module } from '@nestjs/common';
|
|
84
|
+
import { ClientsModule, Transport } from '@nestjs/microservices';
|
|
85
|
+
|
|
86
|
+
@Module({
|
|
87
|
+
imports: [
|
|
88
|
+
ClientsModule.register([
|
|
89
|
+
{
|
|
90
|
+
name: 'MATH_SERVICE',
|
|
91
|
+
transport: Transport.TCP,
|
|
92
|
+
options: { host: 'localhost', port: 3001 },
|
|
93
|
+
},
|
|
94
|
+
]),
|
|
95
|
+
],
|
|
96
|
+
})
|
|
97
|
+
export class AppModule {}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
import { Injectable, Inject } from '@nestjs/common';
|
|
102
|
+
import { ClientProxy } from '@nestjs/microservices';
|
|
103
|
+
import { firstValueFrom } from 'rxjs';
|
|
104
|
+
|
|
105
|
+
@Injectable()
|
|
106
|
+
export class AppService {
|
|
107
|
+
constructor(@Inject('MATH_SERVICE') private client: ClientProxy) {}
|
|
108
|
+
|
|
109
|
+
async calculate(): Promise<number> {
|
|
110
|
+
const pattern = { cmd: 'sum' };
|
|
111
|
+
const payload = [1, 2, 3, 4, 5];
|
|
112
|
+
return firstValueFrom(this.client.send<number>(pattern, payload));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Event-Based Pattern
|
|
118
|
+
|
|
119
|
+
### Handler
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
@EventPattern('user_created')
|
|
123
|
+
async handleUserCreated(@Payload() data: CreateUserEvent) {
|
|
124
|
+
await this.usersService.processNewUser(data);
|
|
125
|
+
}
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Emitter
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
async createUser(dto: CreateUserDto) {
|
|
132
|
+
const user = await this.usersRepository.create(dto);
|
|
133
|
+
this.client.emit('user_created', new CreateUserEvent(user));
|
|
134
|
+
return user;
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## Accessing Context
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
import { Ctx, Payload, NatsContext } from '@nestjs/microservices';
|
|
142
|
+
|
|
143
|
+
@MessagePattern('time.us.*')
|
|
144
|
+
getDate(@Payload() data: any, @Ctx() context: NatsContext) {
|
|
145
|
+
console.log(`Subject: ${context.getSubject()}`);
|
|
146
|
+
return new Date().toISOString();
|
|
147
|
+
}
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## Async Configuration
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
ClientsModule.registerAsync([
|
|
154
|
+
{
|
|
155
|
+
name: 'MATH_SERVICE',
|
|
156
|
+
imports: [ConfigModule],
|
|
157
|
+
useFactory: async (configService: ConfigService) => ({
|
|
158
|
+
transport: Transport.TCP,
|
|
159
|
+
options: {
|
|
160
|
+
host: configService.get('MATH_HOST'),
|
|
161
|
+
port: configService.get('MATH_PORT'),
|
|
162
|
+
},
|
|
163
|
+
}),
|
|
164
|
+
inject: [ConfigService],
|
|
165
|
+
},
|
|
166
|
+
]);
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
## Timeout Handling
|
|
170
|
+
|
|
171
|
+
```typescript
|
|
172
|
+
import { timeout } from 'rxjs/operators';
|
|
173
|
+
|
|
174
|
+
this.client
|
|
175
|
+
.send<number>({ cmd: 'sum' }, [1, 2, 3])
|
|
176
|
+
.pipe(timeout(5000));
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## TLS Support
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
// Server
|
|
183
|
+
const app = await NestFactory.createMicroservice<MicroserviceOptions>(
|
|
184
|
+
AppModule,
|
|
185
|
+
{
|
|
186
|
+
transport: Transport.TCP,
|
|
187
|
+
options: {
|
|
188
|
+
tlsOptions: {
|
|
189
|
+
key: fs.readFileSync('key.pem'),
|
|
190
|
+
cert: fs.readFileSync('cert.pem'),
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
);
|
|
195
|
+
|
|
196
|
+
// Client
|
|
197
|
+
ClientsModule.register([
|
|
198
|
+
{
|
|
199
|
+
name: 'SERVICE',
|
|
200
|
+
transport: Transport.TCP,
|
|
201
|
+
options: {
|
|
202
|
+
tlsOptions: {
|
|
203
|
+
ca: [fs.readFileSync('ca.pem')],
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
},
|
|
207
|
+
]);
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
## Key Points
|
|
211
|
+
|
|
212
|
+
- `@MessagePattern()` for request-response (requires reply)
|
|
213
|
+
- `@EventPattern()` for fire-and-forget events
|
|
214
|
+
- Client connection is lazy (connects on first call)
|
|
215
|
+
- Use `firstValueFrom` to convert Observable to Promise
|
|
216
|
+
- TCP is the default transport
|
|
217
|
+
|
|
218
|
+
<!--
|
|
219
|
+
Source references:
|
|
220
|
+
- https://docs.nestjs.com/microservices/basics
|
|
221
|
+
-->
|