Optical
Aberration

Going Serverless with Hummingbird

Along with Hummingbird, my web application framework, comes HummingbirdLambda. This allows you to take your Hummingbird application and run it on an AWS Lambda with API Gateway as an interface.

As long as all the services your application uses are accessible from AWS Lambda and your application doesn't have any internal persistent state moving to Lambda should be fairly easy. You should be able to move your application setup, middleware, routes to your Lambda with very little change.

In a standard server based Hummingbird application you create your HBApplication, set it up and then call HBApplication.start. Instead in a Hummingbird Lambda you create an HBLambda object which is initialized with an already created HBApplication which you then setup. Do NOT call HBApplication.start on this though.

Where you were doing the following for your server app.

let app = HBApplication()
// set encoder and decoder
app.encoder = JSONEncoder()
app.decoder = JSONDecoder()
// middleware
app.middleware.add(HBLogRequestsMiddleware(.debug))
app.router.get("/") { _ in
    return "hello"
}
...
app.start()
app.wait()

On Lambda you would replace this with

struct MyHandler: HBLambda {
    // define input and output
    typealias In = APIGateway.Request
    typealias Out = APIGateway.Response
    
    init(_ app: HBApplication) {
        app.encoder = JSONEncoder()
        app.decoder = JSONDecoder()
        // middleware
        app.middleware.add(HBLogRequestsMiddleware(.debug))
        app.router.get("/") { _ in
            return "hello"
        }
        ...
    }
}

Lambda.run { context in
    return HBLambdaHandler<MyHandler>(context: context)
}

The exact same application setup is used it is just inside the MyHandler.init function. In this situation we are using an APIGateway REST interface to invoke the Lambda so we need to define the In and Out types to be the APIGateway input and output types. You can set these to APIGateway.V2.Request and APIGateway.V2.Response if you want to use an APIGateway HTML interface.

Building and uploading

You will need to build your Lambda with the swift-aws-lambda-runtime. There are numerous articles on doing this. You can read Fabian Fett's article here for details on how to build and upload your Lambda.

APIGateway

Once you have a Lambda uploaded, you need to setup your API Gateway interface. Go to the APIGateway console and create a new REST API. Once in the editor for your API you need to replicate the routes you setup in your Hummingbird Lambda and have them invoke the Lambda.

Click on the "Actions" dropdown.

You create path elements through Create Resource. If you have a route "/user" create a resource with path "user". If you have another route "/user/:id" create a resource inside the "user" resource with the path "{id}". This is the syntax for URI parameters. For each path you have methods, select the path and then choose Create Method, select the method you want in the dropdown. In the method creation dialog choose Lambda Function, enable Use Lambda Proxy Integration and select the Lambda Function you setup earlier. Once you have setup a method you can add validation for request inputs. This isn't truely necessary as Hummingbird will do this for you, but it does mean not invoking your Lambda when clients send bad requests which is a cost saving for you.

Once your resources and methods are entered select Deploy API. You should be able to invoke your Hummingbird Lambda using the endpoint provided to you and hey presto you've gone serverless.

Example

The hummingbird-examples repository has two examples: todos-dynamodb and todos-lambda. These are both the same application one written to be run as a server and one to be run serverless on AWS Lambda. The code for these two examples is almost exactly the same. There is one minor difference where I needed to workout the host name. The Lambda example also includes a Swagger file you can use to initialize your API Gateway setup with the required resources and methods. You just need to wire up the methods to your Lambda. Please take a look and see how easy it can be to go serverless.