Server-Side Swift With Vapor, AWS Fargate, and the AWS Cloud Development Kit

Get started with server-side Swift today

Yuri Ferretti
Better Programming

--

Photo by the author.

Have you ever thought about how to write your back-end applications using Swift without having to worry about scaling, patching, and other cumbersome infrastructure tasks? If you answered yes, this article is for you.

Requirements

For this tutorial, you need to have an AWS account to deploy your Vapor server. Also, you need to install and configure AWS CDK, TypeScript, Docker, and Vapor.

1. Creating Your Vapor Server App

Vapor is a web framework written in Swift that allows you to develop expressive and Swifty server-side applications.

After installing Vapor, open a terminal window, select a folder where your new project will live, and run the following command:

$ vapor new CDKSwift -n

This will create a new folder called CDKSwift with a Vapor “hello world” template and all the required components to run your server. Now let's open the project by entering the CDKSwift folder and running:

$ vapor xcode

It will open Xcode. At that point, you should have a folder structure that looks like this:

Project navigator folder structure of your new Vapor app.

Open the routes.swift source file and change its contents to the following:

This code:

  1. Configures an endpoint that will respond to HTTP GET requests on <hostname>/ and return an I'm the root path message.
  2. Configures another endpoint that will respond to HTTP GET requests on <hostname>/hello and return Hello, AWS. We haven’t gotten to AWS yet, but we will shortly.

Let's test if everything is working as expected. In Xcode, hit the play button or CMD + R. After the build succeeds, you should see the following message in your Xcode console:

[ NOTICE ] Server starting on http://127.0.0.1:8080

Go back to your terminal window and run:

$ curl localhost:8080

You should see I'm the root path in your terminal. Now run:

$ curl localhost:8080/hello

And you should be able to see Hello, AWS!!!.

Pretty neat, eh? This shows that your Vapor server is up and running. Now we can jump into the next challenge.

2. Containers Everywhere

To run our Vapor server on the cloud, we'll be using Docker containers. The official Docker documentation describes a container as:

“A standard unit of software that packages up code and all its dependencies so the application runs quickly and reliably from one computing environment to another. A Docker container image is a lightweight, standalone, executable package of software that includes everything needed to run an application: code, runtime, system tools, system libraries and settings.”

Some benefits of using containers are:

  • More efficient use of system resources
  • Faster delivery cycles (as you'll notice by the end of this article)
  • Application portability

So install Docker and look for the Dockerfile that lives in the root of your project folder. This file is a "recipe" for Docker to build your container with all the resources required to run your Vapor server.

Feel free to read the file contents to see what the "recipe" for building your application looks like. I'm going to skip the details for this tutorial, but in summary, the recipe will copy all your project contents to an image containing a Swift 5.2 toolchain and build it. Then it will use the generated executable and define an entry point that will run your server in production mode at the address 0.0.0.0 and port 8080.

Before building and running your Docker image, kill the Xcode process that you spun up later. Go to your terminal and run:

$ lsof -i tcp:8080

This will list processes running on TCP port 8080, and if you see any running, grab the number in the PID column and run the following command to kill it:

$ sudo kill -9 <PID>

Now you can build your Docker image. To do so, go to your project root folder and run:

$ docker build -t vapor-image .

After the build succeeds, run your container and check if everything is working as expected by running:

$ docker run --rm -p 8080:8080 -it vapor-image

This command will run your container and bind the port 8080 from your container to your localhost:8080 address. After running it, you should see the following terminal output:

[ NOTICE ] Server starting on http://0.0.0.0:8080

Run the same curl commands you ran before to check if your server is running as expected. If so, let's build the infrastructure that will back our server.

3. AWS Fargate and AWS CDK

Now it's time to start thinking about the cloud infrastructure where our app will be deployed. Wouldn't be nice if we could invest our main focus on developing the app itself and not the infrastructure aspects like scaling, security, and provisioning? Fear not: AWS Fargate is here to help.

In AWS Fargate docs’ own words:

“AWS Fargate is a serverless compute engine for containers that works with both Amazon Elastic Container Service (ECS) and Amazon Elastic Kubernetes Service (EKS). Fargate makes it easy for you to focus on building your applications. Fargate removes the need to provision and manage servers, lets you specify and pay for resources per application, and improves security through application isolation by design.

Fargate allocates the right amount of compute, eliminating the need to choose instances and scale cluster capacity. You only pay for the resources required to run your containers, so there is no over-provisioning and paying for additional servers.”

This sounds like a very good candidate for our app compute platform. So let's start writing our infrastructure code and deploy it.

Yes, you read that right. I said, “Let's write the infrastructure code.”

In July 2019, AWS launched its Cloud Development Kit (CDK), a toolkit that enables you to define your infrastructure in a code-first approach. This way, you can easily define your infrastructure resources using many programming languages, leverage code versioning, and avoid copying and pasting a lot of ARNs in AWS console.

To proceed to the next steps, you should have an AWS account configured. Then you need to install AWS CDK. Near the bottom of the linked page, you will find how to install CDK and its prerequisites. Make sure to use TypeScript as the programming language since we'll be using it in this tutorial.

Jump back to your terminal window and navigate to your project root directory. Then run:

#1 $ mkdir cdk
#2 $ cd cdk
#3 $ cdk init app --language typescript
  1. Create a new directory called cdk inside your project root folder.
  2. Enter the created cdk folder.
  3. Create a new cdk app template prepared for the TypeScript language.

This last command will install a lot of Node.js dependencies. After that is done, you should have a project structure that looks like this:

Open the bin/cdk.ts file (I suggest using VSCode or a text editor other than Xcode) and you should see the following code:

Change Line 6 to new CdkStack(app, 'VaporSwiftStack'); to change the stack name to VaporSwiftStack. CDK uses a concept from AWS called stack that encloses and encapsulates a set of related resources in your cloud infrastructure.

Now jump into lib/cdk-stack.ts and you should see something like this:

This file defines a CdkStack class, and we will define all the other resources required to create our infrastructure inside the class constructor.

Now jump back to the terminal and run:

$ npm install @aws-cdk/aws-ecr-assets

This will install a CDK module capable of uploading our Docker image to the AWS Elastic Container Registry. In order to do that, modify your class to the following:

  1. Import the DockerImageAsset class from our recently installed module.
  2. Import the path module so we can manipulate file paths.
  3. Create a new instance of DockerImageAsset that will be responsible for uploading our image to ECR.
  4. The directory property is the local directory that has our Dockerfile that will be built.
  5. The exclude property will exclude the specified folders or files from our image build.
  6. Set our repository name to vapor-swift-image-repo.

Since we will need to upload assets like our Docker image, we first need to bootstrap our CDK application with our AWS account. To do so, run the following in your terminal:

 $ cdk bootstrap

You will see a lot of resources being created and finally something like this as the output:

 ⏳  Bootstrapping environment aws://<acount-id>/us-east-1...
CDKToolkit: creating CloudFormation changeset...
[██████████████████████████████████████████████████████████] (3/3)
✅ Environment aws://<account-id>/us-east-1 bootstrapped.

So far, so good. Let's continue with our CdkStack class. Before that, let's install two more required CDK modules in our project. Reach your terminal window and run:

$ npm install @aws-cdk/aws-ecs @aws-cdk/aws-ecs-patterns

Now jump back to the lib/cdk-stack.ts source file and change it to the following:

  1. Import the AWS ECS module.
  2. Import the ECS patterns module that has the pattern we will use to deploy and run our Docker image.
  3. Create a cluster that will contain a task running our Docker image and therefore our Vapor app.
  4. Create an ApplicationLoadBalancedFargateService (more on that later).
  5. Set the cluster we just defined in our Fargate definition.
  6. Set the maximum memory to 512 Mb.
  7. Define that we will only have one task running with our service.
  8. Define our service name.
  9. Set the image to be run as our previously created ECR asset.
  10. Set our container port to 8080 (more on that later).
  11. Set the load balancer that will run in front of our Fargate service to be visible outside our VPC.

An ApplicationLoadBalancedFargateService is a deployment pattern that will create an ECS task (our service running in our Docker container) managed by Fargate and put it behind an Application Load Balancer. This way, any traffic sent to the load balancer DNS will be redirected to the containerPort defined in point No. 10.

As you may have noticed, you can configure Fargate service properties like CPU, memory, the number of tasks, and a lot more. Please visit this AWS CDK page to learn more about all the options you can define.

Now let's build our infrastructure code. Go back to your terminal and run:

$ npm run build

If everything was installed and imported correctly, the TypeScript compiler should emit no errors. Now let’s deploy our infrastructure by running:

$ cdk deploy --require-approval never

This will generate a cloud formation template change set and deploy our code without asking if it can create or update IAM roles and policies.

It will take a while and you will start seeing the same output that the docker build command generated in Section 2. After the image is built, you will see an output like the following:

CdkStack: creating CloudFormation changeset...
[██████████████████████▎···································] (15/39)
9:59:18 PM | UPDATE_IN_PROGRESS | AWS::CloudFormation::Stack | CdkStack
10:00:15 PM | CREATE_IN_PROGRESS | AWS::EC2::VPCGatewayAttachment | Cluster/Vpc/VPCGW
10:00:15 PM | CREATE_IN_PROGRESS | AWS::EC2::Subnet | Cluster/Vpc/PublicS
ubnet2/Subnet
10:00:15 PM | CREATE_IN_PROGRESS | AWS::IAM::Policy | VaporSwiftFargate/.
..Role/DefaultPolicy
10:00:15 PM | CREATE_IN_PROGRESS | AWS::EC2::Subnet | Cluster/Vpc/Private
Subnet1/Subnet
10:00:16 PM | CREATE_IN_PROGRESS | AWS::EC2::Subnet | Cluster/Vpc/PublicS
ubnet1/Subnet
10:00:19 PM | CREATE_IN_PROGRESS | AWS::EC2::SecurityGroup | VaporSwiftFargate/L
B/SecurityGroup
10:00:20 PM | CREATE_IN_PROGRESS | AWS::EC2::SecurityGroup | VaporSwiftFargate/S
ervice/SecurityGroup

This shows all the resources being created in your AWS account.

When the resource creation finishes deploying, you will see the following output:

✅  CdkStackOutputs:
CdkStack.VaporSwiftFargateLoadBalancerDNS91F0D848 = CdkSt-Vapor-WFNZA891WCKL-597636731.us-east-1.elb.amazonaws.com
CdkStack.VaporSwiftFargateServiceURL2080FDCA = http://CdkSt-Vapor-WFNZA891WCKL-597636731.us-east-1.elb.amazonaws.com

Finally, grab the HTTP URL in the output list (this is your load balancer address — and your address will be different from this one) and check if the service is up by running the following in your terminal:

$ curl http://CdkSt-Vapor-WFNZA891WCKL-597636731.us-east-1.elb.amazonaws.com

And you should see:

$ I'm the root path

Then append /hello to the URL path and run curl again:

$ curl http://CdkSt-Vapor-WFNZA891WCKL-597636731.us-east-1.elb.amazonaws.com/hello

You should receive:

$ Hello, AWS!!!

Awesome! You have a Vapor server running in AWS. Pretty easy, pretty fast, minimal effort, and you didn’t have to reach the AWS console.

Now I recommend playing around with your Vapor server and then redeploying it. Add other routes for other HTTP methods like POST and PUT, and check what else you can do.

Before wrapping up, I must remind you to clean up your AWS-created resources. Otherwise, you'll be billed for their usage. To delete your stack, reach your terminal and run:

$ cdk destroy

Wrapping Up

In this tutorial, you:

1. Created a fully functional Vapor server capable of responding in multiple routes.

2. Built and ran a Docker container to back your Vapor server.

3. Deployed the container to AWS Fargate using AWS CDK.

Next Steps

In the next tutorial, we will update our Vapor app to use AWS DynamoDB as the database and create CRUD operations.

Thanks for reading this article! If you have any suggestions or comments, please let me know.

--

--