@aws/nx-plugin-mcp 0.119.0 → 0.121.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 (89) hide show
  1. package/bin/aws-nx-mcp.js +23 -35
  2. package/docs/guides/astro-docs.mdx +142 -0
  3. package/docs/guides/connection/py-agent-a2a.mdx +105 -0
  4. package/docs/guides/connection/py-agent-mcp.mdx +147 -0
  5. package/docs/guides/connection/react-agui.mdx +240 -0
  6. package/docs/guides/connection/react-fastapi.mdx +1476 -0
  7. package/docs/guides/connection/react-py-agent.mdx +198 -0
  8. package/docs/guides/connection/react-smithy.mdx +1355 -0
  9. package/docs/guides/connection/react-trpc.mdx +447 -0
  10. package/docs/guides/connection/react-ts-agent.mdx +198 -0
  11. package/docs/guides/connection/smithy-rdb.mdx +161 -0
  12. package/docs/guides/connection/trpc-rdb.mdx +127 -0
  13. package/docs/guides/connection/ts-agent-a2a.mdx +106 -0
  14. package/docs/guides/connection/ts-agent-mcp.mdx +144 -0
  15. package/docs/guides/connection/ts-agent-rdb.mdx +141 -0
  16. package/docs/guides/connection/ts-mcp-server-rdb.mdx +135 -0
  17. package/docs/guides/connection.mdx +126 -0
  18. package/docs/guides/docker-bundling.mdx +403 -0
  19. package/docs/guides/fastapi.mdx +509 -0
  20. package/docs/guides/license.mdx +293 -0
  21. package/docs/guides/nx-generator.mdx +588 -0
  22. package/docs/guides/py-agent.mdx +483 -0
  23. package/docs/guides/py-mcp-server.mdx +161 -0
  24. package/docs/guides/python-lambda-function.mdx +207 -0
  25. package/docs/guides/python-project.mdx +228 -0
  26. package/docs/guides/react-website-auth.mdx +216 -0
  27. package/docs/guides/react-website.mdx +421 -0
  28. package/docs/guides/runtime-config.mdx +312 -0
  29. package/docs/guides/terraform-project.mdx +317 -0
  30. package/docs/guides/trpc.mdx +816 -0
  31. package/docs/guides/ts-agent.mdx +436 -0
  32. package/docs/guides/ts-lambda-function.mdx +218 -0
  33. package/docs/guides/ts-mcp-server.mdx +169 -0
  34. package/docs/guides/ts-nx-plugin.mdx +159 -0
  35. package/docs/guides/ts-rdb.mdx +759 -0
  36. package/docs/guides/ts-smithy-api.mdx +661 -0
  37. package/docs/guides/typescript-infrastructure.mdx +408 -0
  38. package/docs/guides/typescript-project.mdx +312 -0
  39. package/docs/guides/workspace.mdx +181 -0
  40. package/docs/snippets/agent/architecture.mdx +72 -0
  41. package/docs/snippets/agent/bedrock-deployment.mdx +172 -0
  42. package/docs/snippets/agent/runtime-arn.mdx +64 -0
  43. package/docs/snippets/api/api-architecture.mdx +93 -0
  44. package/docs/snippets/api/api-choice-note.mdx +6 -0
  45. package/docs/snippets/api/cors-configuration-cdk-note.mdx +25 -0
  46. package/docs/snippets/api/cors-configuration-terraform-note.mdx +28 -0
  47. package/docs/snippets/api/shared-constructs.mdx +38 -0
  48. package/docs/snippets/api/type-safe-api-integrations.mdx +643 -0
  49. package/docs/snippets/api/waf-configuration.mdx +37 -0
  50. package/docs/snippets/connection/a2a-infrastructure.mdx +63 -0
  51. package/docs/snippets/connection/lambda-rdb-ssl-requirements.mdx +40 -0
  52. package/docs/snippets/connection/mcp-server-rdb-ssl-requirements.mdx +35 -0
  53. package/docs/snippets/connection/rdb-api-infrastructure.mdx +72 -0
  54. package/docs/snippets/connection/react-agent-infrastructure.mdx +61 -0
  55. package/docs/snippets/connection/strands-agent-rdb-ssl-requirements.mdx +35 -0
  56. package/docs/snippets/lambda-function/architecture.mdx +36 -0
  57. package/docs/snippets/lambda-function/deploying-your-function.mdx +118 -0
  58. package/docs/snippets/mcp/architecture.mdx +58 -0
  59. package/docs/snippets/mcp/assistant-docs.mdx +10 -0
  60. package/docs/snippets/mcp/bedrock-deployment.mdx +167 -0
  61. package/docs/snippets/mcp/config.mdx +13 -0
  62. package/docs/snippets/mcp/configuration-py.mdx +42 -0
  63. package/docs/snippets/mcp/configuration-ts.mdx +53 -0
  64. package/docs/snippets/mcp/observability.mdx +8 -0
  65. package/docs/snippets/mcp/shared-constructs.mdx +32 -0
  66. package/docs/snippets/pdk-migration/example/01-migrate-api.mdx +602 -0
  67. package/docs/snippets/pdk-migration/example/02-migrate-website.mdx +915 -0
  68. package/docs/snippets/pdk-migration/example/03-migrate-infra.mdx +161 -0
  69. package/docs/snippets/pdk-migration/example/04-deploy.mdx +229 -0
  70. package/docs/snippets/pdk-migration/faq/aws-arch.mdx +17 -0
  71. package/docs/snippets/pdk-migration/faq/cdk-graph.mdx +29 -0
  72. package/docs/snippets/pdk-migration/faq/infrastructure-python-java.mdx +19 -0
  73. package/docs/snippets/pdk-migration/faq/pdk-nag.mdx +15 -0
  74. package/docs/snippets/pdk-migration/faq/pipeline.mdx +15 -0
  75. package/docs/snippets/pdk-migration/faq/type-safe-api.mdx +310 -0
  76. package/docs/snippets/pdk-migration/faq/use-of-projen.mdx +15 -0
  77. package/docs/snippets/prerequisites.mdx +20 -0
  78. package/docs/snippets/required-prerequisites.mdx +12 -0
  79. package/docs/snippets/shared-constructs.mdx +40 -0
  80. package/docs/snippets/tools/acurl.mdx +73 -0
  81. package/docs/snippets/ts-bundle.mdx +14 -0
  82. package/package.json +1 -1
  83. package/src/py/agent/schema.json +1 -1
  84. package/src/py/fast-api/schema.json +2 -2
  85. package/src/py/mcp-server/schema.json +1 -1
  86. package/src/smithy/ts/api/schema.json +2 -2
  87. package/src/trpc/backend/schema.json +2 -2
  88. package/src/ts/agent/schema.json +1 -1
  89. package/src/ts/mcp-server/schema.json +1 -1
@@ -0,0 +1,816 @@
1
+ ---
2
+ title: tRPC
3
+ description: Reference documentation for tRPC
4
+ generator: ts#trpc-api
5
+ ---
6
+ import { FileTree, Tabs, TabItem } from '@astrojs/starlight/components';
7
+ import Link from '@components/link.astro';
8
+ import RunGenerator from '@components/run-generator.astro';
9
+ import GeneratorParameters from '@components/generator-parameters.astro';
10
+ import NxCommands from '@components/nx-commands.astro';
11
+ import Infrastructure from '@components/infrastructure.astro';
12
+ import Snippet from '@components/snippet.astro';
13
+ import OptionFilter from '@components/option-filter.astro';
14
+
15
+ [tRPC](https://trpc.io/) is a framework for building APIs in TypeScript with end-to-end type safety. Using tRPC, updates to API operation inputs and outputs are immediately reflected in client code and are visible in your IDE without the need to rebuild your project.
16
+
17
+ The tRPC API generator creates a new tRPC API with AWS CDK or Terraform infrastructure setup. The generated backend uses AWS Lambda for serverless deployment, exposed via an AWS API Gateway API, and includes schema validation using [Zod](https://zod.dev/). It sets up [AWS Lambda Powertools](https://docs.powertools.aws.dev/lambda/typescript/latest/) for observability, including logging, AWS X-Ray tracing and Cloudwatch Metrics.
18
+
19
+ ## Usage
20
+
21
+ ### Generate a tRPC API
22
+
23
+ You can generate a new tRPC API in two ways:
24
+
25
+ <RunGenerator generator="ts#trpc-api" />
26
+
27
+ ### Options
28
+
29
+ <GeneratorParameters generator="ts#trpc-api" />
30
+
31
+ <Snippet name="api/api-choice-note" />
32
+
33
+ :::tip[API Type]
34
+ Select `ServerlessApiGatewayRestApi` (default) as your `computeType` if you would like to use [tRPC Subscriptions](https://trpc.io/docs/server/subscriptions) to stream responses.
35
+ :::
36
+
37
+ :::tip[Integration Pattern]
38
+ The `integrationPattern` option defaults to `isolated`, which creates one Lambda per tRPC procedure. Select `shared` if you would prefer a single shared Lambda handler for the whole API, with optional per-procedure overrides.
39
+ :::
40
+
41
+ ## Generator Output
42
+
43
+ The generator will create the following project structure in the `<directory>/<api-name>` directory:
44
+
45
+ <FileTree>
46
+ - src
47
+ - init.ts Backend tRPC initialisation
48
+ - handler.ts Lambda handler entrypoint
49
+ - router.ts tRPC router definition
50
+ - schema Schema definitions using Zod
51
+ - echo.ts Example definitions for the input and output of the "echo" procedure
52
+ - z-async-iterable.ts Zod helper for subscriptions (REST API only)
53
+ - procedures Procedures (or operations) exposed by your API
54
+ - echo.ts Example procedure
55
+ - middleware
56
+ - error.ts Middleware for error handling
57
+ - logger.ts middleware for configuring AWS Powertools for Lambda logging
58
+ - tracer.ts middleware for configuring AWS Powertools for Lambda tracing
59
+ - metrics.ts middleware for configuring AWS Powertools for Lambda metrics
60
+ - local-server.ts tRPC standalone adapter entrypoint for local development server
61
+ - client
62
+ - index.ts Type-safe client for machine-to-machine API calls
63
+ - tsconfig.json TypeScript configuration
64
+ - project.json Project configuration and build targets
65
+
66
+ </FileTree>
67
+
68
+ ### Infrastructure
69
+
70
+ <Snippet name="shared-constructs" />
71
+
72
+ <Snippet name="api/shared-constructs" />
73
+
74
+ #### Architecture
75
+
76
+ <Snippet name="api/api-architecture" />
77
+
78
+ ## Implementing your tRPC API
79
+
80
+ At a high-level, tRPC APIs consist of a router which delegates requests to specific procedures. Each procedure has an input and output, defined as a Zod schema.
81
+
82
+ ### Schema
83
+
84
+ The `src/schema` directory contains the types that are shared between your client and server code. In this package, these types are defined using [Zod](https://zod.dev/), a TypeScript-first schema declaration and validation library.
85
+
86
+ An example schema might look as follows:
87
+
88
+ ```ts
89
+ import { z } from 'zod';
90
+
91
+ // Schema definition
92
+ export const UserSchema = z.object({
93
+ name: z.string(),
94
+ height: z.number(),
95
+ dateOfBirth: z.string().datetime(),
96
+ });
97
+
98
+ // Corresponding TypeScript type
99
+ export type User = z.TypeOf<typeof UserSchema>;
100
+ ```
101
+
102
+ Given the above schema, the `User` type is equivalent to the following TypeScript:
103
+
104
+ ```ts
105
+ interface User {
106
+ name: string;
107
+ height: number;
108
+ dateOfBirth: string;
109
+ }
110
+ ```
111
+
112
+ Schemas are shared by both server and client code, providing a single place to update when making changes to the structures used in your API.
113
+
114
+ Schemas are automatically validated by your tRPC API at runtime, which saves hand-crafting custom validation logic in your backend.
115
+
116
+ Zod provides powerful utilities to combine or derive schemas such as `.merge`, `.pick`, `.omit` and more. You can find more information on the [Zod documentation website](https://zod.dev/?id=basic-usage).
117
+
118
+ ### Router and Procedures
119
+
120
+ Your tRPC router is defined in `src/router.ts`, which registers all procedures. Each procedure defines the expected input, output, and implementation. The Lambda handler entry point is in `src/handler.ts`, which forwards requests to your router.
121
+
122
+ The sample router generated for you has a single operation, called `echo`:
123
+
124
+ ```ts
125
+ import { echo } from './procedures/echo.js';
126
+
127
+ export const appRouter = router({
128
+ echo,
129
+ });
130
+ ```
131
+
132
+ The example `echo` procedure is generated for you in `src/procedures/echo.ts`:
133
+
134
+ ```ts
135
+ export const echo = publicProcedure
136
+ .input(EchoInputSchema)
137
+ .output(EchoOutputSchema)
138
+ .query((opts) => ({ result: opts.input.message }));
139
+ ```
140
+
141
+ To break down the above:
142
+
143
+ - `publicProcedure` defines a public method on the API, including the middleware set up in `src/middleware`. This middleware includes AWS Lambda Powertools integration for logging, tracing and metrics.
144
+ - `input` accepts a Zod schema which defines the expected input for the operation. Requests sent for this operation are automatically validated against this schema.
145
+ - `output` accepts a Zod schema which defines the expected output for the operation. You will see type errors in your implementation if you don't return an output which conforms to the schema.
146
+ - `query` accepts a function which defines the implementation for your API. This implementation receives `opts`, which contains the `input` passed to your operation, as well as other context set up by middleware, available in `opts.ctx`. The function passed to `query` must return an output which conforms to the `output` schema.
147
+
148
+ The use of `query` to define the implementation indicates that the operation is not mutative. Use this to define methods to retrieve data. To implement a mutative operation, use the `mutation` method instead.
149
+
150
+ If you add a new procedure, make sure you register it by adding it to the router in `src/router.ts`.
151
+
152
+ <OptionFilter when={{ computeType: 'ServerlessApiGatewayRestApi' }} description="Streaming subscriptions — REST API only, uses SSE">
153
+ ### Subscriptions (Streaming)
154
+
155
+ tRPC subscriptions allow you to stream data from the server to the client using [Server-Sent Events (SSE)](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events). When you select `ServerlessApiGatewayRestApi` as your compute type, the generator automatically configures the infrastructure required for streaming, as well as a streaming Lambda handler and the `ZodAsyncIterable` schema helper.
156
+
157
+ To define a subscription procedure, use the `.subscription` method with an async generator function. Use the `ZodAsyncIterable` helper from `src/schema/z-async-iterable.ts` to define the output schema:
158
+
159
+ ```ts
160
+ import { publicProcedure } from '../init.js';
161
+ import { z } from 'zod';
162
+ import { ZodAsyncIterable } from '../schema/z-async-iterable.js';
163
+
164
+ const InputSchema = z.object({ query: z.string() });
165
+ const ChunkSchema = z.object({ text: z.string() });
166
+
167
+ export const myStream = publicProcedure
168
+ .input(InputSchema)
169
+ .output(
170
+ ZodAsyncIterable({
171
+ yield: ChunkSchema,
172
+ }),
173
+ )
174
+ .subscription(async function* (opts) {
175
+ // Yield data to the client as it becomes available
176
+ for (const chunk of await getResults(opts.input.query)) {
177
+ yield { text: chunk };
178
+ }
179
+ });
180
+ ```
181
+
182
+ Register the subscription in your router just like any other procedure:
183
+
184
+ ```ts
185
+ export const appRouter = router({
186
+ echo,
187
+ myStream,
188
+ });
189
+ ```
190
+
191
+ The generated infrastructure uses a streaming Lambda handler with `ResponseTransferMode.STREAM` in API Gateway for all REST API operations, which enables subscriptions to work alongside regular queries and mutations.
192
+ </OptionFilter>
193
+
194
+ ## Customising your tRPC API
195
+
196
+ ### Errors
197
+
198
+ In your implementation, you can return error responses to clients by throwing a `TRPCError`. These accept a `code` which indicates the type of error, for example:
199
+
200
+ ```ts
201
+ throw new TRPCError({
202
+ code: 'NOT_FOUND',
203
+ message: 'The requested resource could not be found',
204
+ });
205
+ ```
206
+
207
+ ### Organising Your Operations
208
+
209
+ As your API grows, you may wish to group related operations together.
210
+
211
+ You can group operations together using nested routers, for example:
212
+
213
+ ```ts
214
+ import { getUser } from './procedures/users/get.js';
215
+ import { listUsers } from './procedures/users/list.js';
216
+
217
+ const appRouter = router({
218
+ users: router({
219
+ get: getUser,
220
+ list: listUsers,
221
+ }),
222
+ ...
223
+ })
224
+ ```
225
+
226
+ Clients then receive this grouping of operations, for example invoking the `listUsers` operation in this case might look as follows:
227
+
228
+ ```ts
229
+ client.users.list.query();
230
+ ```
231
+
232
+ ### Logging
233
+
234
+ The AWS Lambda Powertools logger is configured in `src/middleware/logger.ts`, and can be accessed in an API implementation via `opts.ctx.logger`. You can use this to log to CloudWatch Logs, and/or control additional values to include in every structured log message. For example:
235
+
236
+ ```ts {5}
237
+ export const echo = publicProcedure
238
+ .input(...)
239
+ .output(...)
240
+ .query(async (opts) => {
241
+ opts.ctx.logger.info('Operation called with input', opts.input);
242
+
243
+ return ...;
244
+ });
245
+ ```
246
+
247
+ For more information about the logger, please refer to the [AWS Lambda Powertools Logger documentation](https://docs.powertools.aws.dev/lambda/typescript/latest/core/logger/).
248
+
249
+ ### Recording Metrics
250
+
251
+ AWS Lambda Powertools metrics are configured in `src/middleware/metrics.ts`, and can be accessed in an API implementation via `opts.ctx.metrics`. You can use this to record metrics in CloudWatch without the need to import and use the AWS SDK, for example:
252
+
253
+ ```ts {5}
254
+ export const echo = publicProcedure
255
+ .input(...)
256
+ .output(...)
257
+ .query(async (opts) => {
258
+ opts.ctx.metrics.addMetric('Invocations', 'Count', 1);
259
+
260
+ return ...;
261
+ });
262
+ ```
263
+
264
+ For more information, please refer to the [AWS Lambda Powertools Metrics documentation](https://docs.powertools.aws.dev/lambda/typescript/latest/core/metrics/).
265
+
266
+ ### Fine-tuning X-Ray Tracing
267
+
268
+ The AWS Lambda Powertools tracer is configured in `src/middleware/tracer.ts`, and can be accessed in an API implementation via `opts.ctx.tracer`. You can use this to add traces with AWS X-Ray to provide detailed insights into the performance and flow of API requests. For example:
269
+
270
+ ```ts {5-7}
271
+ export const echo = publicProcedure
272
+ .input(...)
273
+ .output(...)
274
+ .query(async (opts) => {
275
+ const subSegment = opts.ctx.tracer.getSegment()!.addNewSubsegment('MyAlgorithm');
276
+ // ... my algorithm logic to capture
277
+ subSegment.close();
278
+
279
+ return ...;
280
+ });
281
+ ```
282
+
283
+ For more information, please refer to the [AWS Lambda Powertools Tracer documentation](https://docs.powertools.aws.dev/lambda/typescript/latest/core/tracer/).
284
+
285
+ ### Implementing Custom Middleware
286
+
287
+ You can add additional values to the context provided to procedures by implementing middleware.
288
+
289
+ As an example, let's implement some middlware to extract some details about the calling user from our API in `src/middleware/identity.ts`.
290
+
291
+ <OptionFilter when={{ auth: 'IAM' }} description="Identity middleware example for IAM-authenticated APIs">
292
+ This example walks through identity middleware for `IAM` authentication. We look up the caller in Cognito using the sub extracted from the API Gateway event.
293
+
294
+ First, we define what we'll add to the context:
295
+
296
+ ```ts
297
+ export interface IIdentityContext {
298
+ identity?: {
299
+ sub: string;
300
+ username: string;
301
+ };
302
+ }
303
+ ```
304
+
305
+ Note that we define an additional _optional_ property to the context. tRPC manages ensuring that this is defined in procedures which have correctly configured this middleware.
306
+
307
+ Next, we'll implement the middlware itself. This has the following structure:
308
+
309
+ ```ts
310
+ export const createIdentityPlugin = () => {
311
+ const t = initTRPC.context<...>().create();
312
+ return t.procedure.use(async (opts) => {
313
+ // Add logic here to run before the procedure
314
+
315
+ const response = await opts.next(...);
316
+
317
+ // Add logic here to run after the procedure
318
+
319
+ return response;
320
+ });
321
+ };
322
+ ```
323
+
324
+ In our case, we want to extract details about the calling Cognito user. We'll do that by extracting the user's subject ID (or "sub") from the API Gateway event, and retrieving user details from Cognito. The implementation varies depending on whether the event was provided to our function by a REST API or an HTTP API:
325
+
326
+ <Tabs syncKey="http-rest">
327
+ <TabItem label="REST API" _filter={{ computeType: 'ServerlessApiGatewayRestApi' }}>
328
+ ```ts
329
+ import { CognitoIdentityProvider } from '@aws-sdk/client-cognito-identity-provider';
330
+ import { initTRPC, TRPCError } from '@trpc/server';
331
+ import { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';
332
+ import { APIGatewayProxyEvent } from 'aws-lambda';
333
+
334
+ export interface IIdentityContext {
335
+ identity?: {
336
+ sub: string;
337
+ username: string;
338
+ };
339
+ }
340
+
341
+ export const createIdentityPlugin = () => {
342
+ const t = initTRPC.context<IIdentityContext & CreateAWSLambdaContextOptions<APIGatewayProxyEvent>>().create();
343
+
344
+ const cognito = new CognitoIdentityProvider();
345
+
346
+ return t.procedure.use(async (opts) => {
347
+ const cognitoAuthenticationProvider = opts.ctx.event.requestContext?.identity?.cognitoAuthenticationProvider;
348
+
349
+ let sub: string | undefined = undefined;
350
+ if (cognitoAuthenticationProvider) {
351
+ const providerParts = cognitoAuthenticationProvider.split(':');
352
+ sub = providerParts[providerParts.length - 1];
353
+ }
354
+
355
+ if (!sub) {
356
+ throw new TRPCError({
357
+ code: 'FORBIDDEN',
358
+ message: `Unable to determine calling user`,
359
+ });
360
+ }
361
+
362
+ const { Users } = await cognito.listUsers({
363
+ // Assumes user pool id is configured in lambda environment
364
+ UserPoolId: process.env.USER_POOL_ID!,
365
+ Limit: 1,
366
+ Filter: `sub="${sub}"`,
367
+ });
368
+
369
+ if (!Users || Users.length !== 1) {
370
+ throw new TRPCError({
371
+ code: 'FORBIDDEN',
372
+ message: `No user found with subjectId ${sub}`,
373
+ });
374
+ }
375
+
376
+ // Provide the identity to other procedures in the context
377
+ return await opts.next({
378
+ ctx: {
379
+ ...opts.ctx,
380
+ identity: {
381
+ sub,
382
+ username: Users[0].Username!,
383
+ },
384
+ },
385
+ });
386
+ });
387
+ };
388
+ ```
389
+ </TabItem>
390
+ <TabItem label="HTTP API" _filter={{ computeType: 'ServerlessApiGatewayHttpApi' }}>
391
+ ```ts
392
+ import { CognitoIdentityProvider } from '@aws-sdk/client-cognito-identity-provider';
393
+ import { initTRPC, TRPCError } from '@trpc/server';
394
+ import { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';
395
+ import { APIGatewayProxyEventV2WithIAMAuthorizer } from 'aws-lambda';
396
+
397
+ export interface IIdentityContext {
398
+ identity?: {
399
+ sub: string;
400
+ username: string;
401
+ };
402
+ }
403
+
404
+ export const createIdentityPlugin = () => {
405
+ const t = initTRPC.context<IIdentityContext & CreateAWSLambdaContextOptions<APIGatewayProxyEventV2WithIAMAuthorizer>>().create();
406
+
407
+ const cognito = new CognitoIdentityProvider();
408
+
409
+ return t.procedure.use(async (opts) => {
410
+ const cognitoIdentity = opts.ctx.event.requestContext?.authorizer?.iam
411
+ ?.cognitoIdentity as unknown as
412
+ | {
413
+ amr: string[];
414
+ }
415
+ | undefined;
416
+
417
+ const sub = (cognitoIdentity?.amr ?? [])
418
+ .flatMap((s) => (s.includes(':CognitoSignIn:') ? [s] : []))
419
+ .map((s) => {
420
+ const parts = s.split(':');
421
+ return parts[parts.length - 1];
422
+ })?.[0];
423
+
424
+ if (!sub) {
425
+ throw new TRPCError({
426
+ code: 'FORBIDDEN',
427
+ message: `Unable to determine calling user`,
428
+ });
429
+ }
430
+
431
+ const { Users } = await cognito.listUsers({
432
+ // Assumes user pool id is configured in lambda environment
433
+ UserPoolId: process.env.USER_POOL_ID!,
434
+ Limit: 1,
435
+ Filter: `sub="${sub}"`,
436
+ });
437
+
438
+ if (!Users || Users.length !== 1) {
439
+ throw new TRPCError({
440
+ code: 'FORBIDDEN',
441
+ message: `No user found with subjectId ${sub}`,
442
+ });
443
+ }
444
+
445
+ // Provide the identity to other procedures in the context
446
+ return await opts.next({
447
+ ctx: {
448
+ ...opts.ctx,
449
+ identity: {
450
+ sub,
451
+ username: Users[0].Username!,
452
+ },
453
+ },
454
+ });
455
+ });
456
+ };
457
+ ```
458
+ </TabItem>
459
+ </Tabs>
460
+ </OptionFilter>
461
+
462
+ <OptionFilter when={{ auth: 'Cognito' }} description="Identity middleware example for Cognito-authenticated APIs">
463
+ When you deploy with `auth: 'Cognito'`, the API Gateway Cognito User Pools authorizer verifies the JWT that the caller supplies in the `Authorization` header and places the verified claims on the Lambda event at `event.requestContext.authorizer.claims`. Our middleware just reads those claims — no extra AWS SDK calls, no manual JWT verification.
464
+
465
+ First, we define what we'll add to the context:
466
+
467
+ ```ts
468
+ export interface IIdentityContext {
469
+ identity?: {
470
+ sub: string;
471
+ username: string;
472
+ };
473
+ }
474
+ ```
475
+
476
+ Note that we define an additional _optional_ property on the context. tRPC manages ensuring that this is defined in procedures which have correctly configured this middleware.
477
+
478
+ Next, the middleware itself. The generator wires the authorizer to accept ID tokens, so `cognito:username` is the canonical username claim; we fall back to `username` for access tokens in case you reconfigure the authorizer:
479
+
480
+ ```ts
481
+ import { initTRPC, TRPCError } from '@trpc/server';
482
+ import { CreateAWSLambdaContextOptions } from '@trpc/server/adapters/aws-lambda';
483
+ import { APIGatewayProxyEvent } from 'aws-lambda';
484
+
485
+ export interface IIdentityContext {
486
+ identity?: {
487
+ sub: string;
488
+ username: string;
489
+ };
490
+ }
491
+
492
+ export const createIdentityPlugin = () => {
493
+ const t = initTRPC
494
+ .context<IIdentityContext & CreateAWSLambdaContextOptions<APIGatewayProxyEvent>>()
495
+ .create();
496
+
497
+ return t.procedure.use(async (opts) => {
498
+ const claims = opts.ctx.event.requestContext?.authorizer?.claims as
499
+ | Record<string, string>
500
+ | undefined;
501
+
502
+ const sub = claims?.sub;
503
+ const username = claims?.['cognito:username'] ?? claims?.username;
504
+
505
+ if (!sub || !username) {
506
+ throw new TRPCError({
507
+ code: 'FORBIDDEN',
508
+ message: 'Unable to determine calling user',
509
+ });
510
+ }
511
+
512
+ return await opts.next({
513
+ ctx: {
514
+ ...opts.ctx,
515
+ identity: {
516
+ sub,
517
+ username,
518
+ },
519
+ },
520
+ });
521
+ });
522
+ };
523
+ ```
524
+
525
+ You can then mix the plugin into any procedure that needs the caller's identity:
526
+
527
+ ```ts
528
+ import { publicProcedure } from '../init.js';
529
+ import { createIdentityPlugin } from '../middleware/identity.js';
530
+ import { z } from 'zod';
531
+
532
+ export const me = publicProcedure
533
+ .concat(createIdentityPlugin())
534
+ .output(z.object({ sub: z.string(), username: z.string() }))
535
+ .query(({ ctx }) => ({
536
+ sub: ctx.identity!.sub,
537
+ username: ctx.identity!.username,
538
+ }));
539
+ ```
540
+
541
+ :::tip[Verifying the token]
542
+ You don't need `aws-jwt-verify` or any other JWT-verification library here — the API Gateway Cognito User Pools authorizer has already verified the signature, issuer, audience, and expiry by the time your Lambda runs. If any of those checks fail, API Gateway returns `401 Unauthorized` and your handler is never invoked.
543
+ :::
544
+ </OptionFilter>
545
+
546
+ ## Deploying your tRPC API
547
+
548
+ The tRPC API generator creates CDK or Terraform infrastructure as code based on your selected `iacProvider`. You can use this to deploy your tRPC API.
549
+
550
+ <Infrastructure>
551
+ <Fragment slot="cdk">
552
+ The CDK construct for deploying your API lives in the `common/constructs` folder. You can consume this in a CDK application, for example:
553
+
554
+ <OptionFilter when={{ auth: ['IAM', 'Custom'] }} description="CDK usage for IAM or Custom authentication">
555
+ ```ts {6-8}
556
+ import { MyApi } from ':my-scope/common-constructs';
557
+
558
+ export class ExampleStack extends Stack {
559
+ constructor(scope: Construct, id: string) {
560
+ // Add the api to your stack
561
+ const api = new MyApi(this, 'MyApi', {
562
+ integrations: MyApi.defaultIntegrations(this).build(),
563
+ });
564
+ }
565
+ }
566
+ ```
567
+
568
+ :::caution[Custom Lambda Authorizer]
569
+ When using `Custom` auth, the construct creates a Lambda Authorizer internally from the generated `src/authorizer.ts` file, which **denies all requests by default**. You must implement your authorization logic in that file before your API will accept any traffic.
570
+ :::
571
+ </OptionFilter>
572
+
573
+ <OptionFilter when={{ auth: 'Cognito' }} description="CDK usage with Cognito authentication — pass the identity construct">
574
+ ```ts {6,9}
575
+ import { MyApi, UserIdentity } from ':my-scope/common-constructs';
576
+
577
+ export class ExampleStack extends Stack {
578
+ constructor(scope: Construct, id: string) {
579
+ // Add the api to your stack
580
+ const identity = new UserIdentity(this, 'Identity');
581
+
582
+ const api = new MyApi(this, 'MyApi', {
583
+ integrations: MyApi.defaultIntegrations(this).build(),
584
+ identity,
585
+ });
586
+ }
587
+ }
588
+ ```
589
+
590
+ The `UserIdentity` construct can be generated using the <Link path="/guides/react-website-auth">`ts#react-website-auth` generator</Link>.
591
+ </OptionFilter>
592
+
593
+ This sets up your API infrastructure, including an AWS API Gateway REST or HTTP API, AWS Lambda functions for business logic, and authentication based on your chosen `auth` method.
594
+
595
+ <Snippet name="api/cors-configuration-cdk-note" />
596
+ </Fragment>
597
+ <Fragment slot="terraform">
598
+ The Terraform modules for deploying your API are in the `common/terraform` folder. You can use this in a Terraform configuration.
599
+
600
+ The API module stages its Lambda deployment zip in a shared S3 asset bucket — see the <Link path="/guides/terraform-project">Terraform infrastructure guide</Link> for details. Instantiate the `core/asset-bucket` module once per deployment and pass its `bucket_name` output into every API / Lambda module via the `asset_bucket_name` input:
601
+
602
+ <OptionFilter when={{ auth: ['IAM', 'Custom'] }} description="Terraform usage for IAM or Custom authentication">
603
+ ```hcl {1-3, 8}
604
+ module "asset_bucket" {
605
+ source = "../../common/terraform/src/core/asset-bucket"
606
+ }
607
+
608
+ module "my_api" {
609
+ source = "../../common/terraform/src/app/apis/my-api"
610
+
611
+ asset_bucket_name = module.asset_bucket.bucket_name
612
+
613
+ # Environment variables for the Lambda function
614
+ env = {
615
+ ENVIRONMENT = var.environment
616
+ LOG_LEVEL = "INFO"
617
+ }
618
+
619
+ # Additional IAM policies if needed
620
+ additional_iam_policy_statements = [
621
+ # Add any additional permissions your API needs
622
+ ]
623
+
624
+ tags = local.common_tags
625
+ }
626
+ ```
627
+ </OptionFilter>
628
+
629
+ <OptionFilter when={{ auth: 'Cognito' }} description="Terraform usage with Cognito authentication — supply user pool and client">
630
+ ```hcl {1-3, 8-9}
631
+ module "asset_bucket" {
632
+ source = "../../common/terraform/src/core/asset-bucket"
633
+ }
634
+
635
+ module "my_api" {
636
+ source = "../../common/terraform/src/app/apis/my-api"
637
+
638
+ asset_bucket_name = module.asset_bucket.bucket_name
639
+
640
+ user_pool_id = local.user_pool_id
641
+ user_pool_client_ids = [local.client_id]
642
+
643
+ # Environment variables for the Lambda function
644
+ env = {
645
+ ENVIRONMENT = var.environment
646
+ LOG_LEVEL = "INFO"
647
+ }
648
+
649
+ # Additional IAM policies if needed
650
+ additional_iam_policy_statements = [
651
+ # Add any additional permissions your API needs
652
+ ]
653
+
654
+ tags = local.common_tags
655
+ }
656
+ ```
657
+
658
+ You can set up the Cognito User Pool and Client using the appropriate Terraform resources or modules.
659
+ </OptionFilter>
660
+
661
+ This sets up:
662
+
663
+ 1. An AWS Lambda function that serves all tRPC procedures
664
+ 2. API Gateway HTTP/REST API as the function trigger
665
+ 3. IAM roles and permissions
666
+ 4. CloudWatch log group
667
+ 5. X-Ray tracing configuration
668
+ 6. CORS configuration
669
+
670
+ <Snippet name="api/cors-configuration-terraform-note" />
671
+
672
+ The Terraform module provides several outputs you can use:
673
+
674
+ ```hcl
675
+ # Access the API endpoint
676
+ output "api_url" {
677
+ value = module.my_api.stage_invoke_url
678
+ }
679
+
680
+ # Access Lambda function details
681
+ output "lambda_function_name" {
682
+ value = module.my_api.lambda_function_name
683
+ }
684
+
685
+ # Access IAM role for granting additional permissions
686
+ output "lambda_execution_role_arn" {
687
+ value = module.my_api.lambda_execution_role_arn
688
+ }
689
+ ```
690
+
691
+ You can customize CORS settings by passing variables to the module:
692
+
693
+ ```hcl
694
+ module "my_api" {
695
+ source = "../../common/terraform/src/app/apis/my-api"
696
+
697
+ asset_bucket_name = module.asset_bucket.bucket_name
698
+
699
+ # Custom CORS configuration
700
+ cors_allow_origins = ["https://myapp.com", "https://staging.myapp.com"]
701
+ cors_allow_methods = ["GET", "POST", "PUT", "DELETE"]
702
+ cors_allow_headers = [
703
+ "authorization",
704
+ "content-type",
705
+ "x-custom-header"
706
+ ]
707
+
708
+ tags = local.common_tags
709
+ }
710
+ ```
711
+
712
+ <OptionFilter when={{ auth: 'Custom' }} description="Custom Lambda Authorizer usage with Terraform">
713
+ :::caution[Custom Lambda Authorizer]
714
+ When using `Custom` auth, your API is protected by a Lambda Authorizer that **denies all requests by default**. You must implement your authorization logic in the generated `src/authorizer.ts` file before your API will accept any traffic.
715
+ :::
716
+ </OptionFilter>
717
+ </Fragment>
718
+ </Infrastructure>
719
+
720
+ <OptionFilter when={{ computeType: 'ServerlessApiGatewayRestApi' }} description="WAF — REST APIs get a WAF Web ACL by default">
721
+ ### WAF
722
+
723
+ <Snippet name="api/waf-configuration" parentHeading="WAF" />
724
+ </OptionFilter>
725
+
726
+ ### Integrations
727
+
728
+ <Snippet name="api/type-safe-api-integrations" parentHeading="Integrations" />
729
+
730
+ :::tip[CDK Type-Safe Integrations]
731
+ If you selected CDK for your `iacProvider`, when you add or remove a procedure in your tRPC API, these changes will be reflected immediately in the CDK construct without the need to rebuild.
732
+ :::
733
+
734
+ <OptionFilter when={{ auth: 'IAM' }} description="Granting API invoke access — IAM-authenticated APIs only">
735
+ ### Granting Access (IAM Only)
736
+
737
+ You can grant access to your API as follows:
738
+
739
+ <Infrastructure>
740
+ <Fragment slot="cdk">
741
+ ```ts
742
+ api.grantInvokeAccess(myIdentityPool.authenticatedRole);
743
+ ```
744
+ </Fragment>
745
+ <Fragment slot="terraform">
746
+ ```hcl
747
+ # Create an IAM policy to allow invoking the API
748
+ resource "aws_iam_policy" "api_invoke_policy" {
749
+ name = "MyApiInvokePolicy"
750
+ description = "Policy to allow invoking the tRPC API"
751
+
752
+ policy = jsonencode({
753
+ Version = "2012-10-17"
754
+ Statement = [
755
+ {
756
+ Effect = "Allow"
757
+ Action = "execute-api:Invoke"
758
+ Resource = "${module.my_api.api_execution_arn}/*/*"
759
+ }
760
+ ]
761
+ })
762
+ }
763
+
764
+ # Attach the policy to an IAM role (e.g., for authenticated users)
765
+ resource "aws_iam_role_policy_attachment" "api_invoke_access" {
766
+ role = aws_iam_role.authenticated_user_role.name
767
+ policy_arn = aws_iam_policy.api_invoke_policy.arn
768
+ }
769
+
770
+ # Or attach to an existing role by name
771
+ resource "aws_iam_role_policy_attachment" "api_invoke_access_existing" {
772
+ role = "MyExistingRole"
773
+ policy_arn = aws_iam_policy.api_invoke_policy.arn
774
+ }
775
+ ```
776
+
777
+ The key outputs from the API module that you can use for IAM policies are:
778
+
779
+ - `module.my_api.api_execution_arn` - For granting execute-api:Invoke permissions
780
+ - `module.my_api.api_arn` - The API Gateway ARN
781
+ - `module.my_api.lambda_function_arn` - The Lambda function ARN
782
+ </Fragment>
783
+ </Infrastructure>
784
+ </OptionFilter>
785
+
786
+ ### Bundle Target
787
+
788
+ <Snippet name="ts-bundle" />
789
+
790
+ ## Local tRPC Server
791
+
792
+ You can use the `serve` target to run a local server for your API, for example:
793
+
794
+ <NxCommands commands={['serve my-api']} />
795
+
796
+ The entry point for the local server is `src/local-server.ts`.
797
+
798
+ This will automatically reload when you make changes to your API.
799
+
800
+ ## Invoking your tRPC API
801
+
802
+ You can create a tRPC client to invoke your API in a type-safe manner. If you are calling your tRPC API from another backend, you can use the client in `src/client/index.ts`, for example:
803
+
804
+ ```ts
805
+ import { createMyApiClient } from ':my-scope/my-api';
806
+
807
+ const client = createMyApiClient({ url: 'https://my-api-url.example.com/' });
808
+
809
+ await client.echo.query({ message: 'Hello world!' });
810
+ ```
811
+
812
+ If you are calling your API from a React website, consider using the <Link path="guides/connection/react-trpc">Connection</Link> generator to configure the client.
813
+
814
+ ## More Information
815
+
816
+ For more information about tRPC, please refer to the [tRPC documentation](https://trpc.io/docs).