What You’ll Learn
  • How routing works.
  • How to add new routes.
To add custom routes, your project must have been created with >=5.29.0 version of Webiny due to core changes in Pulumi that we cannot transfer to the <=5.29.0 versions.

About
anchor

For the 5.31.0 version of Webiny we have refactored our handler package (@webiny/handler) to use fastify . Fastify has quite extensive documentation , so feel free to check it out if you need to modify our default behavior.

Fastify enables us to:

  • add the possibility to add new routes and event handling
  • consistent request and reply (formerly response in our system) methods throughout the system
  • a lot of request lifecycle events for users to hook into
  • implement some other cloud service, at some point, more easily

The new @webiny/handler package does not differentiate between incoming Lambda requests, that is left to the @webiny/handler-aws package, which exports a few handler possibilities:

  • API Gateway handler
  • Raw handler
  • EventBridge handler
  • SQS handler
  • S3 handler
  • DynamoDB handler

Why All the Different Handlers?
anchor

Well, the API Gateway handler and the Raw handler are actually only ones that are really required. The others are there to help with typings and some other checks.

API Gateway Handler
anchor

This handler uses @fastify/aws-lambda in the background to handle API Gateway events. It transforms APIGatewayEvent into the request which fastify understands, and it is then used throughout the system.

For this handler to work it requires at least one RoutePlugin to be initialized. It always returns LambdaResponse defined in the @fastify/aws-lambda package.

Raw Handler
anchor

This handler uses a nice fastify feature that allows you to run any route you have previously defined. So basically, on initialization of the handler we add some dummy route (something like webiny-raw-event) and then run it via the .inject() method on fastify instance. This is the same procedure being used in the @fastify/aws-lambda package. The difference is that our Raw handler can return either APIGatewayProxyResult or what ever is directly sent from the EventPlugin .

For this handler to work it requires exactly one EventPlugin to be defined. If more, or none, are defined, an error will be thrown.

import { createRawHandler, createRawEventHandler } from "@webiny/handler-aws";

const handler = createRawHandler({
  plugins: [
    createRawEventHandler(async ({ event, context }) => {
      const result = await transformRawEventIntoSomethingUseable(event);

      await pushToSQS(event);
    })
  ]
});

S3 Handler
anchor

This handler works as the Raw handler does, only it expects S3EventHandler plugin.

import { createS3Handler, createS3EventHandler } from "@webiny/handler-aws";

const handler = createS3Handler({
  plugins: [
    createS3EventHandler(async ({ event, context }) => {
      await deleteFileFromDynamoDb({ context, event });
    })
  ]
});

EventBridge Handler
anchor

This handler works as the Raw handler does, only it expects EventBridgeEventHandler plugin.

import { createEventBridgeHandler, createEventBridgeEventHandler } from "@webiny/handler-aws";

const handler = createEventBridgeHandler({
  plugins: [
    createEventBridgeEventHandler(async ({ event, context }) => {
      const result = doSomethingWithEventBridgeEvent({ context, event });

      await storeResultIntoDynamoDB(result);
    })
  ]
});

SQS Handler
anchor

This handler works as the Raw handler does, only it expects SQSEventHandler plugin.

import { createSQSHandler, createSQSEventHandler } from "@webiny/handler-aws";

const handler = createSQSHandler({
  plugins: [
    createSQSEventHandler(async ({ event, context }) => {
      const result = doSomethingWithSQSEvent({ context, event });

      await triggerAnotherLambda(result);
    })
  ]
});

DynamoDB Handler
anchor

This handler works as the Raw handler does, only it expects DynamoDBEventHandler plugin.

import { createDynamoDBHandler, createDynamoDBEventHandler } from "@webiny/handler-aws";

const handler = createDynamoDBHandler({
  plugins: [
    createDynamoDBEventHandler(async ({ event, context }) => {
      const result = doSomethingWithDynamoDBEvent({ context, event });

      await transferResultToAnotherService(result);
    })
  ]
});
You can define multiple routes per handler, but you can define only a single event per handler.

Difference Between createHandler and createEventHandler
anchor

There are two methods of defining the event handling:

  • createHandler
  • createEventHandler

What Is the createHandler Method?
anchor

This method creates the base of Webiny system. It will initialize fastify, context and everything which is defined by plugins that were passed on.

We have built-in methods for different types of events:

  • API Gateway

createApiGatewayHandler if imported from @webiny/handler-aws or createHandler if imported from @webiny/handler-aws/gateway

  • Raw handler

createRawHandler if imported from @webiny/handler-aws or createHandler if imported from @webiny/handler-aws/raw

  • EventBridge handler

createEventBridgeHandler if imported from @webiny/handler-aws or createHandler if imported from @webiny/handler-aws/eventBridge

  • SQS handler

createSQSHandler if imported from @webiny/handler-aws or createHandler if imported from @webiny/handler-aws/sqs

  • S3 handler

createS3Handler if imported from @webiny/handler-aws or createHandler if imported from @webiny/handler-aws/s3

  • DynamoDB handler

createDynamoDBHandler if imported from @webiny/handler-aws or createHandler if imported from @webiny/handler-aws/dynamodb

Depending on your need, you can import which ever is most suitable. You can import methods from the root of the @webiny/handler-aws package.

What Is the createEventHandler Method?
anchor

This method creates the handler for the received event. At this point whole Webiny system is ready - all passed plugins are applied, security checks are done, etc…

We have built-in methods for different types of events:

  • API Gateway

createApiGatewayRoute if imported from @webiny/handler-aws or createRoute if imported from @webiny/handler-aws/gateway

  • Raw handler

createRawEventHandler if imported from @webiny/handler-aws or createEventHandler if imported from @webiny/handler-aws/raw

  • EventBridge handler

createEventBridgeEventHandler if imported from @webiny/handler-aws or createEventHandler if imported from @webiny/handler-aws/eventBridge

  • SQS handler

createSQSEventHandler if imported from @webiny/handler-aws or createEventHandler if imported from @webiny/handler-aws/sqs

  • S3 handler

createS3EventHandler if imported from @webiny/handler-aws or createEventHandler if imported from @webiny/handler-aws/s3

  • DynamoDB handler

createDynamoDBEventHandler if imported from @webiny/handler-aws or createEventHandler if imported from @webiny/handler-aws/dynamodb

Notice that all event handlers, except one for API Gateway, are named createEventHandler (or createSomethingEventHandler when importing from root).

The naming convention indicates the amount of event handlers you can define, so route is multiple whereas event is single.

Adding New Routes
anchor

Adding new routes is quite simple, but you need to add them via both Pulumi code and the RoutePlugin.

The Pulumi code goes into apps/api/webiny.application.ts. You must add the new route there, check out the example below, where we are adding a [POST]/webiny route.

import { createApiApp } from "@webiny/serverless-cms-aws";
import { ApiGraphql } from "@webiny/pulumi-aws";

export default createApiApp({
  pulumi: app => {
    const graphQLModule = app.getModule(ApiGraphql);

    graphQLModule.addRoute({
      // name must be in kebab-case
      name: "webiny",
      // path must start with /
      path: "/webiny",
      // all http methods allowed + ANY to catch all requests
      method: "POST"
    });
  }
});

Next thing you need to do is to add it into the apps/api/graphql/src/index.ts file via the RoutePlugin or createApiGatewayRoute method:

apps/api/graphql/src/index.ts
// ... other imports
import {
  createApiGatewayHandler as createHandler,
  createApiGatewayRoute
} from "@webiny/handler-aws";

export const handler = createHandler({
  plugins: [
    // ... other plugins
    createApiGatewayRoute(({ onPost }) => {
      onPost("/webiny", async (request, reply) => {
        // we can log the whole request body
        console.log(request.body);

        // and we can send some reply
        reply
          .headers({
            "x-route-example": "yes"
          })
          .send({
            everything: {
              ok: true
            }
          });
      });
    })
  ],
  http: { debug }
});
Security checks will be done as on the /graphql endpoint. If you need customizations, feel free to add them.
Routes can be added only when initializing the handler.

Adding Event Handler
anchor

The idea behind all our predefined event handlers is to handle events which are not of API Gateway Event type.

Yes, you can catch API Gateway event like this, but there is no point since we have a handler specifically designed for that. Of course, feel free to use whatever is available and can help you to achieve your goal.

Example
anchor

Let’s say you created a part of code which sends out an SQS message, and you want to have a Lambda which handles that message.

Good example would be if you want to run some calculation, asynchronously, from our GraphQL Lambda. You would insert an SQS Message and in turn it would trigger a Lambda which you have defined. That lambda should have code similar to this:

import { createSQSHandler, createSQSEventHandler } from "@webiny/handler-aws";

const handler = createSQSHandler({
  plugins: [
    // other plugins
    createSQSEventHandler(async ({ request, reply, event, context, lambdaContext }) => {
      // the "context" variable is the same as in our system - as long as you haven't changed Webiny's default plugin loader
      // because it is an sqs event, you know the type of the "event" variable and you can handle it from there
      const result = await someHeavyCalculation(event);

      // maybe store that result into the database?
      await storeResult(result);

      return reply.send({
        ok: true
      });
    })
  ]
});
Note that we do not actually check if the event really is an SQS event. It is expected that you initialize the correct handler for event you are expecting. If you add the wrong event it will not execute.

Event Handler Response
anchor

When handling an event, you can either return the reply object or something else, what ever you like. Basically, when you return the reply, a standard APIGatewayProxyResult is created out of the data, headers and cookies you sent. When you return anything else other than the reply, it is returned as the result of the handler, and the Lambda itself.

For example, you can send plain text or object to get the response of the Lambda without the need to parse the APIGatewayProxyResult.