AWS Lambda Tutorial for Swift: Getting Started
Swift is now available in the world of serverless functions via AWS Lambda! Get started deploying your first on-demand serverless function with our AWS Lambda for Swift tutorial. By Ralph Kuepper.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
AWS Lambda Tutorial for Swift: Getting Started
20 mins
- Getting Started
- Creating your AWS Lambda Function
- Creating Your API Gateway
- Configuring Your API’s Routes
- Using Swift with Lambda and AWS
- Testing AWS Lambda Functions Locally
- Writing a Swift App for AWS Lambda
- Creating Your Function’s Model
- Writing Your Function’s Body
- Getting the Function Running on AWS
- Uploading Your Function to AWS Lambda
- Using Additional Services With AWS Lambda
- Where to Go From Here?
Swift is now available in the world of serverless functions, specifically Amazon Web Services (AWS) Lambda. In this AWS Lambda Tutorial for Swift, you’ll write a simple currency conversion function that takes a value and returns the corresponding value for another currency.
Lambda is a platform provided by AWS that allows functions to run on demand. Multiple functions with a sole purpose support an application by responding to events as they happen — resources are computed, a server is spun up and your code runs using AWS resources. This is an excellent solution for applications with unpredictable workloads and scalability requirements.
Your project will be a serverless function for AWS Lambda using Swift. Along the way, you’ll learn how:
- Swift runs on AWS Lambda
- To set up your AWS account
- Writing an AWS Lambda function in Swift works
- To connect your AWS Lambda function to the world
Getting Started
Download the starter project by clicking on the Download Materials button at the top or bottom of this tutorial.
You’ll also want to install Docker or use its web interface when prompted later in the tutorial.
Before you start writing actual code, you’ll need to set up your AWS account and its services. If you’re using an account that’s already in use for other projects or has customized settings, you may need to tweak a few settings and permissions. But, every new account will automatically create all the permissions you need for this tutorial.
You’ll need to set up the following services:
- AWS Lambda
- API Gateway
AWS Lambda is the platform this function will run on, while API Gateway connects the function to the world wide web.
Creating your AWS Lambda Function
Start by setting up your AWS Lambda function. Navigate to the Lambda section within AWS. When you first log in to AWS, you’ll find it either in the list of suggested services or by selecting it in the main menu, like this:
Click on Create function and make sure you have Author from scratch selected.
Now fill out the fields of this form with the following values:
- Function name: eurCurrencyConverter
- Runtime: Provide your own bootstrap on Amazon Linux 2
- Permissions: Create a new role with basic AWS Lambda permissions
Click on Create function and wait for AWS to create the function. You can leave the function alone for now.
Creating Your API Gateway
Next, you’ll create an API Gateway to use for interacting with the AWS Lambda function.
Click on Services and select API Gateway. If you have trouble finding it, you can type in the beginning of API Gateway, as shown below:
On the API Gateway, create a new HTTP API by selecting Build in the HTTP API section:
When prompted, name the API “eurConversion” and click Add integration. In the drop-down, select AWS Lambda, and fill out the fields to match your existing AWS Lambda function — AWS will auto-complete for you. It should look like this when done:
Configuring Your API’s Routes
Click Next and configure the routes. Set the path to /convert and set the integration target to the newly configured AWS Lambda integration. Leave the method as ANY since the function serves POST and GET requests.
Click Next again and then define stages like so:
Click Next one more time, confirm your API settings by clicking Create, and your API is ready.
Now that you’ve created a home for your AWS Lambda function, it’s important to understand how Swift and AWS Lambda work together.
Using Swift with Lambda and AWS
AWS Lambda supports a couple programming languages natively. This means you can upload the source code directly and AWS Lambda can compile it on the fly. Unfortunately, this isn’t yet the case for Swift. So, for Swift to run on AWS Lambda, you need to:
- Compile the function as an executable file that runs on Amazon Linux 2.
- Include all dependencies and libraries with the bootstrap file.
You’ll use Docker to do that, and it’ll provide you with a convenient ZIP file of your function and its dependencies that you upload to AWS Lambda.
AWS Lambda does come with a few limitations:
- AWS Lambda functions run for a maximum of 15 minutes.
- A function may take a few extra seconds to run for the first time since AWS Lambda is booting the function. This is also referred to as a “cold start”.
- AWS Lambda is, by definition, stateless; there’s no shared memory among AWS Lambda functions.
- AWS Lambda functions can perform a variety of tasks, so not every AWS Lambda function is a public function. Depending on your use case, you may not even need internet access.
- AWS Lambda functions can use EventLoops, but they’re usually used within a specific context only.
Swift has released the official AWS Lambda runtime, which provides a convenient framework to develop lambda functions. This runtime is the basis for the function you’ll develop in this tutorial.
Testing AWS Lambda Functions Locally
When you develop AWS Lambda functions, testing them is a little different than developing regular applications. All functional tests involve calling the URL: http://localhost:7000/invoke as a POST request. Internally, AWS Lambda is calling the function with parameters depending on the setup. If you’re using AWS Lambda with a trigger from an SQS queue, you’ll deal with different parameters than when using it behind API Gateway.
LOCAL_LAMBDA_SERVER_ENABLED
, with a value of true
for the runtime to start in this mode. You can do this by editing your project’s Run scheme. This isn’t necessary to complete this tutorial since you’ll be using your Lambda API Gateway.Now it’s time to write some code.
Writing a Swift App for AWS Lambda
Open the starter project in Xcode by clicking on Package.swift. Take note of the following two dependencies in this file:
// 1
.package(url: "https://github.com/swift-server/swift-aws-lambda-runtime.git",
.upToNextMajor(from:"0.1.0")),
// 2
.package(url: "https://github.com/swift-server/async-http-client.git",
from: "1.0.0")
The two dependencies are:
- The application needs the AWS Lambda function to have the official AWS Lambda framework available.
- The
AsyncHttpClient
to fetch a document from a server
Next, take a look at main.swift in Sources ▸ EURCurrencyRate. This file defines three global instances of classes you need in your functions:
//1
let jsonEncoder = JSONEncoder()
let jsonDecoder = JSONDecoder()
//2
let httpClient = HTTPClient(eventLoopGroupProvider: .createNew)
defer {
try? httpClient.syncShutdown()
}
Here’s what’s happening in the code above:
-
JSONEncoder
andJSONDecoder
will read and write JSON content. - You need
HTTPClient
to pull the most current exchange rate from an external website. You’re creating a newEventLoop
by using the.createNew
static value for theeventLoopGroupProvider
parameter when initializing yourHTTPClient
.
EventLoop
by default and it doesn’t need to.At line 50, you see the function that AWS Lambda calls through the runtime framework:
Lambda.run {
(context,
request: APIGateway.V2.Request,
callback: @escaping (Result<APIGateway.V2.Response, Error>) -> Void) in
The framework defines input and output types you use in the function and return in the form of a callback function. The context of the request is defined as APIGateway.V2.Request
, which will tell you what kind of a request this is.
You also see a global convert
function, which will create the actual output of the AWS Lambda function on line 46.
You’ll get this function to react to the following two requests:
-
GET
:/convert
: Take the input in form of query parameters:?amount=1
-
POST
:/convert
: Take the input in the form of a JSON body.
Creating Your Function’s Model
Before you write the logic, create the following three types in separate files in EURCurrencyRate:
First, create RateInput.swift for a struct that resembles the input for the POST call:
import Foundation
struct RateInput: Codable {
// 1
let amount: Double
// 2
let currency: String?
}
The two inputs are:
- amount: This is the actual amount of money the function converts.
- currency: This is optional and will default to USD.
Now create a RateOutput.swift for a struct that represents the output of the conversion:
import Foundation
struct RateOutput: Codable {
// 1
let inputAmount: Double
// 2
let rate: Double
// 3
let inputCurrency: String
// 4
let outputAmount: Double
// 5
let outputCurrency: String
}
The variables in this struct represent:
-
inputAmount
: the original amount provided -
rate
: the applied conversion rate -
inputCurrency
: the input currency forinputAmount
-
outputAmount
: the converted amount, usinginputAmount
andrate
-
outputCurrency
: the desired currency
Finally, create a RateResponse.swift file containing the structure for the response from the API you use to determine the exchange rate:
import Foundation
struct RateResponse: Codable {
var rates: [String: Double]
var base: String
}
The API returns many rates; the logic will sort out which one to take.
Writing Your Function’s Body
Next you’ll write the business logic. Write the following code in the Lambda.run function in main.swift:
// 1
switch (request.context.http.path, request.context.http.method) {
// 2
case ("/convert", .GET):
// 3
let amount = Double(request.queryStringParameters?["amount"] ?? "0.0") ?? 0
let desiredCurrency = request.queryStringParameters?["currency"] ?? "USD"
// 4
convert(amount: amount, desiredCurrency: desiredCurrency, callback: callback)
// 5
case ("/convert", .POST):
// 6
if let data = request.body?.data(using: .utf8),
let input = try? jsonDecoder.decode(RateInput.self, from: data) {
convert(
amount: input.amount,
desiredCurrency: input.currency ?? "USD",
callback: callback)
} else {
// 7
callback(.success(APIGateway.V2.Response(statusCode: .badRequest)))
}
default:
// 8
callback(.success(APIGateway.V2.Response(statusCode: .notFound)))
}
Here’s what the code above does:
- Checks the context of this call. Remember AWS Lambda functions are often called internally and not always by a user and a browser.
- If it’s a
GET
call to/convert
, this case applies. - Since it’s a
GET
call, convert the query parameters into variables that you can pass to theconvert
function. - Call the convert function and pass the callback along.
- If it’s a
POST
call, this case applies. - Instead of parsing the variables from the URL, you decode the data from the request body into the
RateInput
struct. If this succeeds, call the convert function as well. - If, for some reason, like invalid JSON, the body doesn’t parse, return a
400: Bad Request
HTTP code. - If it’s a call to a different path or a different type of HTTP call, return a
404: Not Found
HTTP code.
Both functions then call the global convert
function to calculate the output and return it to the runtime.
Write the following code in the convert
function:
// 1
httpClient.get(url: "https://api.exchangeratesapi.io/latest").whenComplete {
result in
switch result {
case .failure(let error):
// 2
callback(.failure(error))
case .success(let response):
// 3
if let data = response.body, response.status == .ok {
let data = try? jsonDecoder.decode(RateResponse.self, from: data)
if let data = data {
// 4
for currency in data.rates.keys where currency == desiredCurrency {
// 5
let rate = Double(data.rates[currency] ?? 0)
// 6
let newAmount = rate * amount
// 7
let output = RateOutput(
inputAmount: amount,
rate: rate,
inputCurrency: "EUR",
outputAmount: newAmount,
outputCurrency: desiredCurrency)
// 8
if let data = try? jsonEncoder.encode(output) {
// 9
callback(.success(APIGateway.V2.Response(
statusCode: .ok,
multiValueHeaders: ["content-type": ["application/json"]],
body: String(decoding: data, as: Unicode.UTF8.self)
)))
}
}
}
callback(.success(APIGateway.V2.Response(statusCode: .badRequest)))
} else {
callback(
.success(APIGateway.V2.Response(statusCode: .internalServerError))
)
}
}
}
Here’s what’s happening in the code above:
- You call an API that returns conversion rates.
- If this fails, you return an error right away, as you can’t calculate anything.
- At this point, you at least have a response from the API, so now you parse it into the previously defined
RateResponse
struct. - You loop through the different rates to find the one specified in the input.
- Convert the value to a double.
- Calculate the new amount.
- Form the output as the
RateOutput
struct. - Convert this output to JSON.
- Call the callback function with the JSON string and a
200: OK
HTTP code.
This function is the heart of your program. Once you write this, you can start compiling! :]
Getting the Function Running on AWS
OK, it’s time to get the function up and running. To deploy to AWS Lambda, compile your application into a binary file that can run on AWS Lambda. The best way to do this is by compiling it on the correct Linux distribution, which you can do through Docker. Amazon Linux 2, the runtime behind AWS Lambda, is available for Swift, and so the command to build your application using Docker looks like this:
docker run \
--rm \
--volume "$(pwd)/:/src" \
--workdir "/src/" \
swift:5.2-amazonlinux2 \
/bin/bash -c "yum -y install libuuid-devel libicu-devel libedit-devel libxml2-devel sqlite-devel python-devel ncurses-devel curl-devel openssl-devel libtool jq tar zip && swift build --product EURCurrencyRate -c release && scripts/package.sh EURCurrencyRate"
This commands does the following:
- run a Docker container.
--rm
tells Docker to delete the container when the container finishes. - Use your src folder in the working directory ($(pwd)) as a volume.
- Set /src/ as the work directory (–workdir).
- Use the Amazon Linux 2 image with Swift pre-installed.
- Then run commands to install required dependencies and compile the code. Afterward, run a small script, package.sh, that Apple provided as part of the AWS Lambda runtime framework. It creates a ZIP file that contains all the files AWS Lambda needs.
Run the command from above and wait until the script finishes. Finally, run the following command:
cp .build/lambda/EURCurrencyRate/lambda.zip .
lambda.zip
file into your main folder so you can grab it from there.Uploading Your Function to AWS Lambda
Now head back over to AWS Lambda and click on the function to open it there. Select Upload a .zip file in the Function code section. Choose the ZIP file you created and upload it. And that’s it! Your lambda function is ready now.
Go back to the API Gateway you created by clicking services and searching for “API Gateway.” Open the API Gateway resource you created and find the “Invoke URL” section.
Copy and paste the URL into your browser and append /convert?amount=10.00. You’ll see the desired output like this:
Your first lambda function is live and deployed! :]
Using Additional Services With AWS Lambda
If you’ve worked with AWS before, you’re well aware of other services you might have an interest in. The ones that are often used in connection with AWS Lambda are SNS, SQS and S3. For those — but also for many other AWS services — check out Soto. It’s the AWS SDK for Swift and can integrate nicely with the Swift Lambda Runtime.
It’s likely you’ll need some configuration via stored credentials in your AWS Lambda function. The easiest way to do this is by pulling these credentials from a Parameter Store through Soto. That way, your credentials aren’t saved in any files but stay in the cloud.
You may also want to use AWS Lambda functions in a different context — for example, as background workers. You can use most of the Server-Side Swift packages available to connect to databases.
Where to Go From Here?
You can download the final project by clicking the Download Materials button at the top and bottom of this page.
In this tutorial, you learned how to create an AWS Lambda function using Swift. Swift has a growing ecosystem of packages and projects, and AWS Lambda is compatible with many of them. If you want to explore writing web applications that might run on AWS Lambda with a more holistic framework, check out Server-Side Swift with Vapor.
If you’re new to web development but have experience with Swift, you’ll find it’s easy to create robust, fully featured web apps and web APIs with Vapor 4.
If you have any questions or comments, please join the forum discussion below!