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?
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
AsyncHttpClientto 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:
-
JSONEncoderandJSONDecoderwill read and write JSON content. - You need
HTTPClientto pull the most current exchange rate from an external website. You’re creating a newEventLoopby using the.createNewstatic value for theeventLoopGroupProviderparameter 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, usinginputAmountandrate -
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
GETcall to/convert, this case applies. - Since it’s a
GETcall, convert the query parameters into variables that you can pass to theconvertfunction. - Call the convert function and pass the callback along.
- If it’s a
POSTcall, this case applies. - Instead of parsing the variables from the URL, you decode the data from the request body into the
RateInputstruct. 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 RequestHTTP code. - If it’s a call to a different path or a different type of HTTP call, return a
404: Not FoundHTTP 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
RateResponsestruct. - 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
RateOutputstruct. - Convert this output to JSON.
- Call the callback function with the JSON string and a
200: OKHTTP code.
This function is the heart of your program. Once you write this, you can start compiling! :]