Skip to main content

Your First Klotho App

This tutorial demonstrates how to create a Go REST API with chi/v5 and use Klotho to transform it into a cloud-native one.

The tutorial will cover the Klotho expose feature that give your existing code cloud native capabilities. We call these Klotho capabilities.

Getting Started

Prerequisites

Application Overview

This sample app has 2 GET endpoints, and is primarily to showcase the experimental go expose capability.

REST API Endpoints

  • GET /hello
  • GET /hello/{name}

This application will utilize the following annotations:

Setting Up

Start by creating a directory for your project.

mkdir klotho-my-first-app  # Fill in wherever you'd like to create your application
cd klotho-my-first-app

Then create a go.mod file in your project directory containing the following dependencies:

go.mod
module example/klotho-my-first-app

go 1.18

require github.com/go-chi/chi/v5 v5.0.8

Then create a go.sum file in your project directory containing the following:

go.sum
github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0=
github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8=

Finally, create the main.go file and add the following code, which will set up the chi/v5 router with the 2 GET endpoints and add the @klotho::expose annotation to http.ListenAndServe:

main.go
package main

import (
"fmt"
"net/http"

"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
)

func main() {
r := chi.NewRouter()
r.Use(middleware.Logger)

r.Get("/hello", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Hello from Klotho!"))
})

r.Get("/hello/{name}", func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(fmt.Sprintf("Hello %s!", chi.URLParam(r, "name"))))
})

fmt.Println("Listening on :3000")

/* @klotho::expose {
* target = "public"
* id = "app"
* }
*/
http.ListenAndServe(":3000", r)
}

Testing locally

One of the most powerful aspects of Klotho is letting you code, debug and iterate locally, knowing that once your ready, the cloud version will behave the same way.

This gives you the fastest possible developer experience without changing how you work.

To run the application locally, in one terminal run:

go run main.go
Listening on :3000

Once the application is running, in another terminal test its endpoints with curl:

curl "localhost:3000/hello"
# -> Hello from Klotho!

curl "localhost:3000/hello/your-name"
# -> Hello your-name!

Once you're done testing, bring the server down (with Ctrl-C in the first terminal).

Compiling with Klotho

Log into Klotho to set up your user profile. This will allow us to support you if you run into any issues, and give you the opportunity to shape the product in this early development stage.

klotho --login # if you haven't already

With the Klotho capabilities added to the application, run Klotho to get the cloud native version of it.

❯ klotho . --app my-first-app --provider aws --outDir _compiled

██╗ ██╗██╗ ██████╗ ████████╗██╗ ██╗ ██████╗
██║ ██╔╝██║ ██╔═══██╗╚══██╔══╝██║ ██║██╔═══██╗
█████╔╝ ██║ ██║ ██║ ██║ ███████║██║ ██║
██╔═██╗ ██║ ██║ ██║ ██║ ██╔══██║██║ ██║
██║ ██╗███████╗╚██████╔╝ ██║ ██║ ██║╚██████╔╝
╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝

Adding resource input_file_dependencies:
Adding resource exec_unit:main
main.go:29:1: Found 2 route(s) on app 'r'
Adding resource gateway:app
Adding resource topology:my-first-app
Adding resource aws_template_data:my-first-app
Adding resource infra_as_code:Pulumi (AWS)
Pulumi.my-first-app.yaml: Make sure to run `pulumi config set aws:region YOUR_REGION --cwd '_compiled/' -s 'my-first-app'` to configure the target AWS region.

The cloud version of the application is saved to the ./_compiled directory, and has everything you need to deploy, run and operate the application.

Visualizing the Cloud Version

Helping you understand what Klotho did, open the ./_compiled/my-first-app.png diagram created alongside the cloud application:

topology diagram showing a lambda that is exposed by an API Gateway

The expose capability created and connected an API Gateway to the go-chi router app running on a Lambda execution unit.

Deploying the Application

Deployment Dependencies

Now that the last-section has produced infrastructure-as-code, it is possible to deploy the cloud-native version of the application. The AWS account set up on your computer will be used.

But first, we need to install and set up a few more dependencies:

  • Docker
  • an AWS account, set up with either:
    • The AWS_ACCESS_KEY_ID & AWS_SECRET_ACCESS_KEY environment variables for a user
    • OR, $HOME/.aws/credentials (eg. via AWS CLI: aws configure) setup
  • Pulumi CLI
    • Follow the Pulumi installation instructions and set up for local usage:
      pulumi login --local
      export PULUMI_CONFIG_PASSPHRASE=""

Deploying with Pulumi

# region should be an aws region, for example us-west-1

pulumi config set aws:region <region> --cwd '_compiled/' -s my-first-app

If this is your first time deploying this application, you will be prompted to create the my-first-app stack. Just press ENTER and the stack will be created for you:

The stack 'my-first-app' does not exist.
If you would like to create this stack now, please press <ENTER>, otherwise press ^C:

Now that the Pulumi stack is created, simply install the project dependencies and deploy.

cd compiled
npm install
pulumi up

You'll see a preview of the changes to be applied to your AWS account: (you'll be able to delete these later on)

Previewing update (my-first-app):
Type Name Plan
+ pulumi:pulumi:Stack my-first-app-my-first-app create
+ ├─ awsx:ecr:Repository my-first-app create
+ │ └─ aws:ecr:LifecyclePolicy my-first-app create
+ ├─ aws:ecr:Repository my-first-app create
+ ├─ aws:cloudwatch:LogGroup main-function-api-lg create
+ ├─ aws:apigateway:RestApi app create
+ │ ├─ aws:apigateway:Resource app{name}/ create
+ │ │ └─ aws:apigateway:Method GET-{name}-6f360 create
+ │ │ └─ aws:apigateway:Integration lambda-GET-{name}-6f360 create
+ │ ├─ aws:apigateway:Method GET-/-8a5ed create
+ │ │ └─ aws:apigateway:Integration lambda-GET-/-8a5ed create
+ │ └─ aws:apigateway:Deployment app-deployment create
+ │ └─ aws:apigateway:Stage app-stage create
+ ├─ aws:iam:Role my-first-app_0d6e4_LambdaExec create
+ │ ├─ aws:iam:Policy my-first-app-main-exec create
+ │ └─ aws:iam:RolePolicyAttachment my-first-app-main-exec create
+ ├─ aws:iam:RolePolicyAttachment my-first-app-main-lambdabasic create
+ ├─ aws:lambda:Function main create
+ ├─ aws:s3:Bucket create
+ ├─ aws:lambda:Permission Get-name-permission create
+ └─ aws:lambda:Permission Get--permission create

Outputs:
apiUrls : [
[0]: output<string>
]
deploymentPortal: "None - Opted out of topology upload by default"

Resources:
+ 21 to create

Do you want to perform this update? [Use arrows to move, enter to select, type to filter]
> yes
no
details

Respond with yes when you're ready and wait for your resources to be created. This usually takes a few minutes. When it's done deploying, you'll see the completion status and the AWS provided API Gateway URL for your API:

Outputs:
apiUrls : [
[0]: "https://qxy99avspl.execute-api.us-east-1.amazonaws.com/stage/"
]

Testing the Cloud Version

After you deploy the application, you'll have a publicly accessible endpoint. Test that the Klotho-powered version behaves the same as the local one rerunning the same tests from before, only this time point them at the deployed version:

APP_URL=<app url from the pulumi output> # including the /stage

curl "$APP_URL/hello"
# -> Hello from Klotho!

curl "$APP_URL/hello/your-name"
# -> Hello your-name!

Cleanup

To clean up your AWS deployment simply run:

cd compiled
pulumi destroy

You will see a Pulumi preview of all the resources pending deletion. Response with yes to begin deleting.

What next?

  • Join Discord and chat with us. What went well, what went poorly, what are you looking forward to?