Skip to main content

Creating a REST API

Creating a Simple Doggie Daycare REST API using Express.js

Overview

This tutorial demonstrates how to transform a plain Express REST API into a cloud-native one. The doggie daycare API can store and retrieve the pet name for each pet owner.

The tutorial will cover 2 Klotho features that give your existing code cloud native capabilities. We call these Klotho capabilities.

Getting Started

Prerequisites

Application Overview

The doggie daycare sample application is an Express app that's exposed through port 3000. The server has an endpoint to store the pet name of an owner, and another endpoint to retrieve it.

This application will utilize the following annotations:

Setting Up

Start by creating and initilizing the Node application:

mkdir klotho-quickstart  # Fill in wherever you'd like to create your application
cd klotho-quickstart
npm init -y

Express App

First, you'll need an Express app to define routes on and listen to a port. Install express as a dependency:

npm install express

The default entrypoint is index.js so we'll start by creating that file with:

index.js
const express = require('express');

// Create a new Express app
const app = express();
// Enable JSON middleware
app.use(express.json());

// Start listening to requests on port 3000, printing a message when it's ready
app.listen(3000, () => console.log('App listening locally on :3000'));

Adding Pet Registration

Next, we'll want to add an owner-to-pet mapping to store registration information.

index.js
const express = require('express');

// Create a new Express app
const app = express();
// Enable JSON middleware
app.use(express.json());

// Create an owner-to-pet map
const petsByOwner = new Map();

// Handler function for associating new owners with their pet
async function addPetName(req, res) {
// The owner's and pet's names are given in the body
const {owner, pet} = req.body;

petsByOwner.set(owner, pet);
res.send(`Added ${pet} as ${owner}'s pet`)
}


// Handler function for retrieving the pet by the owner's name
async function getAllPets(req, res) {
// Convert from Map to an Object for JSON serialization
res.send(Object.fromEntries(petsByOwner.entries()))
}

// Register the handlers on their respective paths
app.get('/pets', getAllPets);
app.post('/pets', addPetName);

// Start listening to requests on port 3000, printing a message when it's ready
app.listen(3000, () => console.log('App listening locally on :3000'));

Testing locally

With the basic API implemented, let's test it:

In one terminal:

node index.js

In another terminal:

curl "localhost:3000/pets"
# -> {} :: We start with no pets registered

curl -X POST "localhost:3000/pets" \
-H 'Content-Type: application/json' \
-d '{"owner": "Alice", "pet": "Fido"}'
# -> Added Fido as Alice's pet

curl "localhost:3000/pets"
# -> {"Alice":"Fido"} :: We see that our newly registered pet is now in the map

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

Adding Klotho Annotations

Now that we have the application working locally, let's add the Klotho annotations to bring our app to the cloud.

Persistence

We want our pet registration to be persisted so that as we release new versions, the registrations remain intact. Below demonstrates modification excepts to the index.js:

/* @klotho::persist {
* id = "petsByOwner"
* }
*/
const petsByOwner = new Map();

// Handler function for associating new owners with their pet
async function addPetName(req, res) {
// The owner's and pet's names are given in the body
const {owner, pet} = req.body;

try {
await petsByOwner.set(owner, pet);
res.send(`Added ${pet} as ${owner}'s pet`)
} catch (error) {
res.status(500).send({error})
}
}


// Handler function for retrieving the pet by the owner's name
async function getAllPets(req, res) {
try {
res.send(Object.fromEntries(await petsByOwner.entries()))
} catch (error) {
res.status(500).send({error})
}
}

In addition to the annotation, we also need to change the get and entries calls to be await'd because they will be network calls in the cloud version. Since they can also fail, wrap in a try block and return a proper response.

Expose API

We also want to expose our pet API so that users can register their pets. Below demonstrates the modification to index.js:

/* @klotho::expose {
* id = "pet-api"
* target = "public"
* }
*/
app.listen(3000, () => console.log('App listening locally on :3000'));

For expose, all we need to do is add the annotation.

Compiling the Application with Klotho

With the changes above, your file should now look similar to:

index.js
const express = require('express');

const app = express();
app.use(express.json());

/* @klotho::persist {
* id = "petsByOwner"
* }
*/
const petsByOwner = new Map();

async function addPetName(req, res) {
try {
await petsByOwner.set(owner, pet);
res.send(`Added ${pet} as ${owner}'s pet`)
} catch (error) {
res.status(500).send({error})
}
}


async function getAllPets(req, res) {
try {
res.send(Object.fromEntries(await petsByOwner.entries()))
} catch (error) {
res.status(500).send({error})
}
}

app.get('/pets', getAllPets);
app.post('/pets', addPetName);

/*
* @klotho::expose {
* id = "pet-api"
* target = "public"
* description = "Exposes the Pet API to the internet"
* }
*/
app.listen(3000, () => console.log('App listening locally on :3000'));

Let's compile this with Klotho to use the annotations to compile the code.

❯ klotho . --app quickstart --provider aws

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

Adding resource input_file_dependencies:
Adding resource exec_unit:main
Found 2 route(s) on server 'app'
Adding resource gateway:pet-api
Adding resource persist_kv:petsByOwner
Adding resource topology:quickstart
Adding resource infra_as_code:Pulumi (AWS)
Pulumi.quickstart.yaml: Make sure to run `pulumi config set aws:region YOUR_REGION --cwd 'compiled/' -s 'quickstart'` to configure the target AWS region.

In the ./compiled folder, you will find the Infrastructure-as-Code (IaC) for your cloud-native application ready for deployment. You will also find a file called quickstart.png, visualizing the topology output of your compiled application:

topology diagram showing the quickstart resources

The topology diagram shows that Klotho has generated IaC targeting AWS to deploy an API Gateway that invokes a Lambda function serving the application, using DynamoDB for persistence.

Deploying the application

To deploy the application, please refer to the Deploying guide.