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