Deploying a TypeScript Fastify API to AWS ECS Fargate using CDK

David
6 min readMar 3, 2023

--

In this blog post, we will deploy a simple HTTP API using Fastify, written in TypeScript to AWS ECS Fargate using AWS CDK. In one of my previous blog posts, I introduced using AWS CDK with TypeScript, check it out first if you haven’t already. I’ll also be following on from another of my blog posts, where I built a multi-stage Docker container that ran a simple Fastify API.

Prerequisites

What is AWS ECS Fargate?

Amazon Elastic Container Service (ECS) is a fully managed container orchestration service provided by AWS. ECS allows you to easily run and scale containerised applications on AWS, and it integrates seamlessly with other AWS services. Fargate is a deployment option for ECS that allows you to run containers without having to manage the underlying infrastructure.

Why use AWS CDK?

AWS Cloud Development Kit (CDK) is an open-source software development framework to define cloud infrastructure in code and provision it through AWS CloudFormation. With the CDK, you can define infrastructure as code using familiar programming languages like TypeScript, Python, or Java. The CDK offers several benefits, including:

  • Easy to use: Developers can use familiar programming languages and modern development tools to define and deploy infrastructure, making it easier to manage infrastructure as code.
  • Reusable: The CDK provides a library of pre-built AWS constructs, making it easy to reuse and share infrastructure code.
  • Scalable: The CDK can be used to manage large-scale infrastructure deployments using the same familiar programming constructs used for smaller deployments.
  • Secure: The CDK enforces best practices for security and compliance.

Getting Started

I won’t assume you’ve followed along with my previous blog posts, so let’s get our project up & running quickly:

Create a new Node.js project

First, create a new directory for your project and initialise a new Node.js project using npm. Run the following commands in your terminal:

npm install -g aws-cdk
mkdir fastify-docker
cd fastify-docker
cdk init app --language=typescript

Install Fastify

Next, install Fastify and save it as a dependency in your project using npm. Run the following command in your terminal:

npm install fastify @types/node --save

Create the “hello world” server

Now, create a new file called src/index.ts in the root of your project directory. This file will contain the code for the "hello world" HTTP server.

import fastify from 'fastify'

const server = fastify()

const { ADDRESS = 'localhost', PORT = '3000' } = process.env;

server.get('/', async (request, reply) => {
return { message: 'Hello world!' }
})

server.listen({ host: ADDRESS, port: parseInt(PORT, 10) }, (err, address) => {
if (err) {
console.error(err)
process.exit(1)
}
console.log(`Server listening at ${address}`)
})

Add a build script

Let’s update package.json to add a simple build script for our API:

{
"scripts": {
"build": "tsc -p tsconfig.json --outDir ./dist"
}
}

The --outDir flag controls the directory where compiled code will be placed.

Create a new Dockerfile

First, create a new file called Dockerfile in the root of your project directory. This file will contain the instructions for building your Docker image.

We’re going to re-use the multi-stage Dockerfile I introduced in my previous blog post, but we’ll modify it to use the npm run build script we added in the previous step.

# Stage 1: Install production dependencies
FROM node:16-alpine as builder

WORKDIR /app

COPY package*.json ./

RUN npm install --production

# Stage 2: Compile our application
FROM node:16-alpine as compiler

WORKDIR /app

COPY package*.json ./

RUN npm install && npm run build

# Stage 3: Create the production image
FROM gcr.io/distroless/nodejs:16

ENV ADDRESS=0.0.0.0 PORT=3000

WORKDIR /app

COPY package*.json ./

COPY --from=builder /app/node_modules .
COPY --from=compiler /app/dist .

CMD ["node", "dist/index.js"]

This Dockerfile contains three stages:

  • Stage 1: Install only production dependencies
  • Stage 2: Compile our application from TypeScript
  • Stage 3: Create the production image

In stage 1, we use the official Node.js 16-alpine image as our base image, set the working directory to /app, copy the package*.json files to the working directory, install dependencies using npm, copy the rest of the files to the working directory, and run the npm run build command. This stage is responsible for building our application.

In stage 2, we are again using the official Node.js 16-alpine image as our base image, but this time we are installing all the necessary development & production dependencies in-order to run npm run build . This stage is responsible for compiling our TypeScript code.

In stage 3, we use the distroless Node.js 16 image as our base image, set the working directory to /app, copy the node_modules and dist folders from the previous stage to the working directory and set the default command to run the node dist/index.js command. This stage is responsible for creating the production image.

Deploying a simple HTTP API to AWS ECS Fargate

We’ll be using the ApplicationLoadBalancedFargateService construct that makes it easy to deploy our service. It takes care of creating and configuring several AWS resources, including:

  • Amazon ECS cluster: A logical grouping of resources that are used to run containerized applications on Fargate.
  • Amazon ECS task definition: A blueprint that describes how a container should be run, including information about the container image, CPU and memory requirements, and networking configuration.
  • Amazon ECS service: A long-running task that runs on Fargate and is managed by ECS.
  • Amazon Elastic Load Balancer (ELB): A load balancer that distributes traffic to the service.
  • Amazon CloudWatch Logs group: A log group where logs generated by the service are stored.

Deploy to ECS Fargate using AWS CDK

We have now built our initial solution in TypeScript and have implemented a multi-stage Dockerfile. Finally, need to update & deploy our stack to AWS using the CDK CLI. Before we do that, we need to make sure that we have configured our AWS credentials and set the default region in the AWS CLI.

The lib/cdk-stack.ts file is where we will define the infrastructure resource for deploying the Fargate ECS CDK construct.

Let’s define the ApplicationLoadBalancedFargateService construct. We will need to import the aws-ecs and aws-ecs-patterns module:

import * as cdk from 'aws-cdk-lib';
import * as ecs from 'aws-cdk-lib/aws-ecs';
import * as ecsPatterns from 'aws-cdk-lib/aws-ecs-patterns';
import { Construct } from 'constructs';

export class MyStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);

const loadBalancedFargateService = new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'Service', {
memoryLimitMiB: 512,
cpu: 256,
taskImageOptions: {
image: ecs.ContainerImage.fromAsset('.'),
},
});

loadBalancedFargateService.targetGroup.configureHealthCheck({
path: '/',
});
}
}

In the updated MyStack class, we have configured the ApplicationLoadBalancedFargateService construct. We define where AWS CDK should look in-order to find the Dockerfile we defined earlier in this post. AWS CDK takes care of building Docker Container and pushing it to a secure AWS ECR for us, during a deployment.

Finally, we configure a health check for the AWS Application Load Balancer, so that it knows the service is healthy and ready to receive traffic.

Deploying AWS CDK

To deploy AWS CDK, we first need to bootstrap our AWS environment. Bootstraping involves creating various resources to facilitate deployments and a new AWS CloudFormation stack that AWS CDK will use to store and manage its deployment artifacts.

Bootstrap AWS CDK

Once we have installed the AWS CLI, we can bootstrap AWS CDK by running the following command:

cdk bootstrap

Note: Running bootstrap more than once on a specific AWS Account & region has no effect.

Deploy Infrastructure Resources

After defining our infrastructure resources, we can deploy them using the AWS CDK CLI. To deploy our resources, run the following command:

cdk deploy

This command will build, package, and deploy our infrastructure resources to AWS.

Once the deployment is complete, you should see an output message that contains the URL of your HTTP API. You can use this URL to test your API by making a GET request to it.

Housekeeping

Once you’ve deployed everything, use the following command to destroy any deployed resources to avoid any unwanted cost:

cdk destroy

Conclusion

In this technical blog post, we walked through the steps of deploying a simple HTTP API to AWS ECS Fargate using the AWS CDKApplicationLoadBalancedFargateService construct. We covered the basics of building a Fastify Docker container using TypeScript, AWS ECS Fargate and then deploying using CDK.

The ApplicationLoadBalancedFargateService construct makes it easy to deploy containerised applications to AWS ECS Fargate. With the CDK, we can define and deploy infrastructure as code using familiar programming languages, making it easier to manage infrastructure at scale.

--

--

David

Coding is both my hobby and my job. I love writing about things I'm working on ❤️