Lambda Handlers
The generated runtime projects include lambda handler wrappers, which can be used to implement API operations for messages received by the server (ie direction
is client_to_server
or bidirectional
). These provide type safety, ensuring that your API handlers make use of inputs defined in your model.
For example:
import { sayHelloHandler } from "myapi-typescript-runtime";
export const handler = sayHelloHandler(async ({ input, sdk, connectionId }) => {
// The server SDK provides type-safety for sending messages to clients
await sdk.sendGreeting(connectionId, { greeting: `Hello ${input.name}!` });
});
Warning
Java is not yet supported.
Warning
Python is not yet supported.
Handler Projects
By configuring handlers.languages
in your TypeSafeWebSocketApiProject
and annotating your operations, you can take advantage of generated handler stubs and generated lambda function CDK constructs to speed up development even further.
Use the @handler
trait, and specify the language you wish to implement this operation in.
@async(direction: "client_to_server")
@handler(language: "typescript")
operation SayHello {
input := {
@required
name: String
}
}
Use the @handler
decorator, and specify the language you wish to implement this operation in.
@async({ direction: "client_to_server" })
@handler({ language: "typescript" })
op SayHello(
name: string,
): void;
Use the x-handler
vendor extension, specifying the language you wish to implement this operation in.
/SayHello:
get:
operationId: SayHello
x-async:
direction: client_to_server
x-handler:
language: typescript
requestBody:
content:
application/json:
schema:
type: object
properties:
name:
type: string
required:
- name
responses: {}
This will give you generated lambda handler stubs which look like the following:
Notice this defines a sayHello
method which provides type-safety for your inputs, and preconfigures the server SDK. You can implement your business logic in there. The lambda handler is wrapped by the sayHelloHandler
wrapper which manages marshalling and demarshalling, as well as the application of "interceptors".
Notice the default INTERCEPTORS
are added to your handler, which provide logging, tracing and metrics from Powertools for AWS Lambda, as well as error handling. For more details about interceptors, refer to the Interceptors section.
import {
sayHelloHandler,
SayHelloChainedHandlerFunction,
INTERCEPTORS,
LoggingInterceptor,
} from "myapi-typescript-runtime";
/**
* Type-safe handler for the SayHello operation
*/
export const sayHello: SayHelloChainedHandlerFunction = async (request) => {
LoggingInterceptor.getLogger(request).info('Start SayHello Operation');
// `input` contains the request input
// `connectionId` is the ID of the connection which sent this request to the server.
// `sdk` is used to send messages to connected clients
const { input, connectionId, sdk } = request;
// TODO: Implement SayHello Operation.
};
/**
* Entry point for the AWS Lambda handler for the SayHello operation.
* The sayHelloHandler method wraps the type-safe handler and manages marshalling inputs and outputs
*/
export const handler = sayHelloHandler(...INTERCEPTORS, sayHello);
Warning
Java is not yet supported.
Warning
Python is not yet supported.
Note
If you wish to deviate from the folder structure of the handlers
projects, or wish to implement your operations in a language not supported by Type Safe API, or through a non-lambda interation you can omit the @handler
trait or x-handler
vendor extension.
You can implement your lambda handlers in any of the supported languages, or mix and match languages for different operations if you prefer.
An example unit test will also be generated for each handler. These unit tests are only generated when the corresponding handler is initially generated, so you can safely delete the generated test if you do not want it.
Tip
Annotate your Smithy service
with the @connectHandler
and @disconnectHandler
traits to generate handlers for the connect and disconnect events. In OpenAPI, use the top level vendor extensions x-connect-handler
and x-disconnect-handler
.
Function CDK Constructs
As well as generating lambda handler stubs, when you use the @handler
Smithy trait or x-handler
OpenAPI vendor extension, your generated CDK infrastructure project will include lambda function CDK constructs with preconfigured paths to your handler distributables. This allows you to quickly add lambda integrations to your API:
import { WebSocketApi, SayHelloFunction } from "myapi-typescript-infra";
new WebSocketApi(this, id, {
authorizer: new WebSocketIamAuthorizer(),
integrations: {
sayHello: {
integration: new WebSocketLambdaIntegration("SayHelloIntegration", new SayHelloFunction(this, "SayHello")),
},
},
});
Warning
Java is not yet supported.
Warning
Python is not yet supported.
Warning
The <Operation>Function
constructs will point to the implementation in the project corresponding to the language
you selected in the @handler
Smithy trait or x-handler
OpenAPI vendor extension. If you relocate your handler implementation and leave the trait a new handler stub will be generated and the construct will point to that. If you remove the @handler
Smithy trait or x-handler
OpenAPI vendor extension from an operation, your generated CDK infrastructure will not include a CDK function construct, and you will need to write your own.
Tip
Annotate your Smithy service
with the @connectHandler
and/or @disconnectHandler
trait to generate $ConnectFunction
and $DisconnectFunction
constructs. In OpenAPI, use the top level vendor extensions x-connect-handler
and x-disconnect-handler
.
Lambda Architectures
Lambda architectures can be configured in the same way as REST APIs. Please refer to the REST API documentation on Lambda Handlers.
Native Dependencies
Native dependencies can be handled in the same way as REST APIs. Please refer to the REST API documentation on Lambda Handlers.
Runtime Versions
Runtime versions can be configured in the same way as REST APIs. Please refer to the REST API documentation on Lambda Handlers.
Handler Router
The lambda handler wrappers can be used in isolation as handlers for separate lambda functions. If you would like to use a single lambda function to serve all requests, you can wrap your individual handlers with a "handler router", for example:
import {
handlerRouter,
sayHelloHandler,
sayGoodbyeHandler,
} from "myapi-typescript-runtime";
import { myInterceptor } from "./interceptors";
import { sayGoodbye } from "./handlers/say-goodbye";
const sayHello = sayHelloHandler(async ({ connectionId, sdk }) => {
await sdk.sendGreeting(connectionId, { greeting: 'Hello!' });
});
export const handler = handlerRouter({
// Interceptors declared in this list will apply to all operations
interceptors: [myInterceptor],
// Assign handlers to each operation here
handlers: {
sayHello,
sayGoodbye,
},
});
Warning
Java is not yet supported.
Warning
Python is not yet supported.
When you use a handler router, you must specify the same lambda function for every integration in your Api
CDK construct. To save typing, you can use the Operations.all
method from your generated runtime package:
import { Operations } from "myapi-typescript-runtime";
import { Api } from "myapi-typescript-infra";
import { WebSocketIamAuthorizer } from "aws-cdk-lib/aws-apigatewayv2-authorizers";
import { WebSocketLambdaIntegration } from "aws-cdk-lib/aws-apigatewayv2-integrations";
import { NodejsFunction } from "aws-cdk-lib/aws-lambda";
new WebSocketApi(this, "Api", {
authorizer: new WebSocketIamAuthorizer(),
// Use the same integration for every operation.
integrations: Operations.all({
integration: new WebSocketLambdaIntegration("RouterIntegration", new NodejsFunction(this, "Router")),
}),
});
Warning
Java is not yet supported.
Warning
Python is not yet supported.
Connect and Disconnect Handlers
Using the @connectHandler
/@disconnectHandler
Smithy traits (or the x-connect-handler
/x-disconnect-handler
vendor extensions in OpenAPI) will generate lambda handler stubs for managing connections.
The $connect
handler is called when a new client attempts to connect to the WebSocket API, and is authenticated. You may optionally choose to deny the connection if you would like to perform additional authorization. Note that the connection is not established (or denied) until the lambda function returns, so you cannot use the Server SDK to send messages to the given connection. You can however use the SDK to send messages to other connections.
Often, the $connect
handler is used to store connection IDs along with metadata such as the authenticated user, and the $disconnect
handler is used to clean up any state pertaining to a connection.
import {
$connectHandler,
$ConnectChainedLambdaHandlerFunction,
INTERCEPTORS,
LoggingInterceptor,
$PendingConnection,
} from 'myapi-typescript-runtime';
import { isAuthenticated } from "./my-auth-utilities";
/**
* Type-safe handler for the $connect event, invoked when a new client connects to the websocket
*/
export const $connect: $ConnectChainedLambdaHandlerFunction = async (request) => {
LoggingInterceptor.getLogger(request).info('Start $connect');
// `connectionId` is the ID of the new connection
// `sdk` is used to send messages to connected clients
// Note that you cannot send messages to the new connection until after this function returns
const { connectionId, sdk } = request;
// TODO: Implement
// Use the below to allow or deny the incoming connection (when the lambda returns).
// The connection is allowed by default.
if (!isAuthenticated(request)) {
$PendingConnection.of(request).deny();
}
};
Warning
Java is not yet supported.
Warning
Python is not yet supported.