Skip to main content

Using Klotho with NestJS and Sequelize

Overview

This tutorial shows how to leverage Klotho's existing support for Express to wire a REST API using NestJS and Sequelize for deployment to the cloud.

Getting Started

Prerequisites

Repository

Clone our sample apps git repo and install the npm packages for the ts-nestjs-sequelize application.

git clone https://github.com/klothoplatform/sample-apps.git
cd sample-apps/ts-nestjs-sequelize
npm install

Application Overview

The NestJS + Sequelize sample application (ts-nestjs-sequelize) is a REST API for managing users built using the NestJS web framework and Sequelize ORM.

Klotho Annotations Used

REST API Endpoints

  • POST /users - Creates a new user in the database from the data supplied in the request body.
  • GET /users - Gets all registered users from the database.
  • GET /users/:id - Gets the user associated with the supplied id path parameter from the database.

Wiring the application for Klotho Compilation

Serving the Application

First, the NestJS application is initialized as an Express app by supplying an ExpressAdapter that wraps an Express app along with the application's root module, AppModule, as arguments to NestFactory.create.

main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ExpressAdapter, NestExpressApplication } from '@nestjs/platform-express';
import * as express from 'express';

async function bootstrap() {
...
const expressApp = express();
const app = await NestFactory.create<NestExpressApplication>(AppModule, new ExpressAdapter(expressApp));
await app.init();
...
}
...

Then the application listens for requests to the NestExpressApplication by invoking app.listen.

The call to app.listen Annotated with @kotho::expose lets Klotho know to expose this application to consumers using an API Gateway.

main.ts
...
async function bootstrap() {
...
/*
* @klotho::expose {
* id = "UsersGateway"
* target = "public"
* }
*/
await app.listen(3000);
...
}
...
Handling Requests

Requests are handled using NestJS Controllers.

Annotating Controller modules using the @klotho::execution_unit annotation defines service boundaries as each Execution Unit is served by a separate unit of compute (e.g. AWS Lambda functions or ECS Fargate services).

users.controller.ts
/* @klotho::execution_unit {
* id = "UsersApi"
* }
*/

import { Controller, Get } from '@nestjs/common';
import { UsersService } from './users.service';

@Controller('users')
export class UsersController {
...
@Get()
async getUsers() {
return await this.appService.findAllUsers()
}
... // Additional handlers
}
Data Persistence

Data is persisted using a Sequelize Model created using a Klotho-annotated instance of Sequelize.

The @klotho::persist annotation informs Klotho to replace the original Sequelize constructor arguments with the connection details for connecting to a cloud-hosted relational database (e.g. AWS RDS).

users.model.ts
...
/* @klotho::persist {
* id = "UsersDB"
* }
*/
const sequelize = new Sequelize(`sqlite::memory:`);
...

const usersModel = sequelize.then(async (client) => {
const usersModel = client.define<Model<InferAttributes<UserModel>>>(
'Users', // 'Users' table schema
{
id: {...},
firstName: {...},
lastName: {...},
},
},
);
await usersModel.sync({ alter: true });
return usersModel;
});
...

Compiling the Sample Application with Klotho

Start by compiling the typescript application into javascript.

npx tsc

The compiled output will be located in the ./dist directory.

Then compile the application with Klotho.

klotho . --app ts-nestjs-sequelize --provider aws
██╗  ██╗██╗      ██████╗ ████████╗██╗  ██╗ ██████╗
██║ ██╔╝██║ ██╔═══██╗╚══██╔══╝██║ ██║██╔═══██╗
█████╔╝ ██║ ██║ ██║ ██║ ███████║██║ ██║
██╔═██╗ ██║ ██║ ██║ ██║ ██╔══██║██║ ██║
██║ ██╗███████╗╚██████╔╝ ██║ ██║ ██║╚██████╔╝
╚═╝ ╚═╝╚══════╝ ╚═════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝

Adding resource input_file_dependencies:
Adding resource exec_unit:UsersAPI
Found 3 route(s) for controller 'UsersController'
Adding resource gateway:UsersGateway
Adding resource persist_orm:usersDB
Adding resource topology:ts-nestjs-sequelize
Adding resource infra_as_code:Pulumi (AWS)

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 ts-nestjs-sequelize.png, visualizing the topology output of your compiled application:

topology diagram showing users gateway exposing users-api lambda that connects to users-db

The topology diagram shows that Klotho has generated IaC targeting AWS to deploy an API Gateway, UsersGateway, that invokes the UsersAPI Lambda function, which writes to and reads from the UsersDB RDS instance.

Examining the persisted section of the Klotho.yaml file generated by Klotho (./compiled/Klotho.yaml), you will see that Postgres will be used as the relational database engine for the Klotho-compiled version of the application.

Klotho.yaml
...
persisted:
UsersDB:
type: rds_postgres
...

Deploying the application

Once the IaC has been generated, you can deploy the cloud-native version of the application with Pulumi.

First, use the Pulumi CLI to set the region aws:region setting for the application's Pulumi stack. This is the AWS region that Pulumi will deploy the application to.

pulumi config set aws:region <region> --cwd './compiled' --stack ts-nestjs-sequelize

Press ENTER () to confirm that you want to create the ts-nestjs-sequelize Pulumi stack.

If you would like to create this stack now, please press <ENTER>, otherwise press ^C:
Created stack 'ts-nestjs-sequelize'

Next, change the current working directory to ./compiled and install the dependencies of the Pulumi application generated by Klotho.

cd compiled
npm install

Then deploy the application by running pulumi up.

pulumi up

Pulumi will display a preview of all the cloud resources it will create as part of the deployment.

Previewing update (ts-nestjs-sequelize)

Type Name Plan
+ pulumi:pulumi:Stack ts-nestjs-sequelize create..
+ ├─ awsx:x:ec2:Vpc ts-nestjs-sequelize create
+ ├─ awsx:ecr:Repository ts-nestjs-sequelize create
+ │ └─ aws:ecr:LifecyclePolicy ts-nestjs-sequelize create
+ ├─ aws:apigateway:RestApi UsersGateway create
+ ├─ aws:apigateway:RestApi UsersGateway create
+ ├─ aws:apigateway:RestApi UsersGateway create
+ ├─ aws:apigateway:RestApi UsersGateway create
+ ├─ aws:apigateway:RestApi UsersGateway create
+ ├─ aws:apigateway:RestApi UsersGateway create
+ ├─ aws:apigateway:RestApi UsersGateway create
+ ├─ aws:apigateway:RestApi UsersGateway create
+ ├─ aws:apigateway:RestApi UsersGateway create
+ ├─ aws:apigateway:RestApi UsersGateway create
+ ├─ aws:apigateway:RestApi UsersGateway create
+ │ └─ aws:ec2:Eip ts-nestjs-sequelize-1 create
+ │ ├─ aws:apigateway:Resource UsersGatewayusers/ create
+ │ ├─ aws:apigateway:Resource UsersGatewayusers/ create
+ │ ├─ aws:apigateway:Resource UsersGatewayusers/ create
+ │ ├─ aws:apigateway:Resource UsersGatewayusers/ create
+ │ ├─ aws:apigateway:Resource UsersGatewayusers/ create
+ │ ├─ aws:apigateway:Resource UsersGatewayusers/ create
+ │ ├─ aws:apigateway:Resource UsersGatewayusers/ create
+ │ ├─ aws:apigateway:Resource UsersGatewayusers/ create
+ │ ├─ aws:apigateway:Resource UsersGatewayusers/ create
+ │ │ └─ aws:ec2:Subnet ts-nestjs-sequelize-private-0 create
+ │ │ └─ aws:apigateway:Method POST-users-7dfb4 create
+ │ │ └─ aws:apigateway:Method POST-users-7dfb4 create
+ │ │ └─ aws:apigateway:Method POST-users-7dfb4 create
+ │ │ └─ aws:apigateway:Method POST-users-7dfb4 create
+ │ │ └─ aws:apigateway:Method POST-users-7dfb4 create
+ │ │ └─ aws:apigateway:Method POST-users-7dfb4 create
+ │ │ └─ aws:ec2:Route ts-nestjs-sequelize-public-1-ig create
+ │ └─ aws:apigateway:Resource UsersGatewayusers/{id}/ create
+ │ └─ aws:apigateway:Resource UsersGatewayusers/{id}/ create
+ │ └─ aws:apigateway:Resource UsersGatewayusers/{id}/ create
+ │ └─ aws:ec2:NatGateway ts-nestjs-sequelize-1 create
+ ├─ aws:s3:Bucket ts-nestjs-sequelize-payloads create
+ ├─ aws:ecr:Repository ts-nestjs-sequelize create
+ ├─ aws:iam:Role usersdb-ormsecretrole create
+ ├─ aws:secretsmanager:Secret usersdb_secret create
+ ├─ aws:secretsmanager:Secret usersdb_secret create
+ ├─ aws:secretsmanager:Secret usersdb_secret create
+ │ │ └─ aws:ec2:Route ts-nestjs-sequelize-private-1-nat-1 create
+ ├─ aws:iam:Role ts-nestjs-sequelize_aa41d_LambdaExec create
+ ├─ aws:iam:RolePolicyAttachment ts-nestjs-sequelize-UsersAPI-lambdabasic create
+ ├─ aws:ec2:SecurityGroup ts-nestjs-sequelize create
+ ├─ aws:ec2:VpcEndpoint s3VpcEndpoint create
+ ├─ aws:ec2:VpcEndpoint dynamodbVpcEndpoint create
+ ├─ aws:ec2:SecurityGroupRule ts-nestjs-sequelize-ingress create
+ ├─ aws:ec2:VpcEndpoint snsVpcEndpoint create
+ ├─ aws:ec2:VpcEndpoint lambdaVpcEndpoint create
+ ├─ aws:ec2:VpcEndpoint lambdaVpcEndpoint create
+ ├─ aws:ec2:VpcEndpoint lambdaVpcEndpoint create
+ │ └─ aws:apigateway:Integration lambda-GET-id-b3dcb create
+ ├─ aws:ec2:VpcEndpoint secretsmanagerVpcEndpoint create
+ ├─ aws:ec2:VpcEndpoint secretsmanagerVpcEndpoint create
+ │ │ └─ aws:apigateway:Integration lambda-POST-users-7dfb4 create
+ ├─ aws:rds:SubnetGroup ts-nestjs-sequelize create
+ ├─ aws:rds:SubnetGroup ts-nestjs-sequelize create
+ ├─ aws:rds:SubnetGroup ts-nestjs-sequelize create
+ pulumi:pulumi:Stack ts-nestjs-sequelize create
+ ├─ aws:rds:Instance usersdb create
+ ├─ aws:secretsmanager:SecretVersion usersdb_secret create
+ ├─ aws:iam:Policy usersdb-ormsecretpolicy create
+ ├─ aws:rds:Proxy usersdb create
+ ├─ aws:iam:RolePolicyAttachment usersdb-ormattach create
+ ├─ aws:rds:ProxyDefaultTargetGroup usersdb create
+ ├─ aws:lambda:Function UsersAPI create
+ ├─ aws:rds:ProxyTarget usersdb create
+ ├─ aws:lambda:Permission Get-users-permission create
+ ├─ aws:lambda:Permission Post-users-permission create
+ └─ aws:lambda:Permission Get-usersid-permission create

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

Resources:
+ 73 to create

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

Select yes from the displayed options to start the deployment process and then wait until Pulumi has completed the deploying the application's stack. The initial deployment usually takes 8-10 minutes as database creation may take several minutes.

When Pulumi has finished deploying, you will see the completion status and the AWS provided API Gateway URL for your API:

Outputs:
apiUrls : [
[0]: "https://<gateway_id>.execute-api.<region>.amazonaws.com/stage"
]
deploymentPortal: "None - Opted out of topology upload by default"

Resources:
+ 22 created

Duration: 8m31s

Testing the Deployed Application

Once the application has been deployed to the cloud, set the GATEWAY_URL environment variable in your terminal with the API Gateway URL output at the end of the deployment step.

export GATEWAY_URL="https://<gateway_id>.execute-api.<region>.amazonaws.com/stage"

Then try out the application's REST endpoints by executing the curl commands below in your terminal.

Create a New User
curl --request POST "${GATEWAY_URL}/users" \
--header 'Content-Type: application/json' \
--data-raw '{ "id": 1, "firstName": "John", "lastName": "Doe" }'
Output
status code: 201
Get a Single User
curl "${GATEWAY_URL}/users/1"
Output
{
"id": 1,
"firstName": "John",
"lastName": "Doe"
}
Get All Users
curl "${GATEWAY_URL}/users"
Output
[
{
"id": 1,
"firstName": "John",
"lastName": "Doe"
}
]

Cleanup

When you're done with the tutorial, you can destroy the created resources by running pulumi destroy from the ./compiled directory and selecting yes when prompted.

pulumi destroy