
This video is only available to subscribers. Start a subscription today to get access to this and 419 other videos.
Swift Web Frameworks
Episode Links
- Source Code
- NSDateFormatter.com - a side project I launched that runs Swift on Linux and the libraries covered in this video
- Frank - Frank is a DSL for quickly writing web applications in Swift
- Currasow - Swift HTTP server using the pre-fork worker model
- Stencil - Stencil is a simple and powerful template language for Swift
Setting up the Virtual Machine
To setup our Vagrant machine, we can define the Vagrantfile
like this:
Vagrant.configure(2) do |config|
config.vm.box = "ubuntu/trusty64"
config.vm.network "forwarded_port", guest: 8000, host: 8082
config.ssh.pty = true
config.vm.synced_folder ".", "/srv/hello-world"
config.vm.provision "shell", privileged: false, inline: <<-SHELL
sudo apt-get update
sudo apt-get install -y git clang libicu-dev nginx
if [[ ! -f swift-DEVELOPMENT-SNAPSHOT-2016-03-01-a-ubuntu14.04.tar.gz ]]
then
wget https://swift.org/builds/development/ubuntu1404/swift-DEVELOPMENT-SNAPSHOT-2016-03-01-a/swift-DEVELOPMENT-SNAPSHOT-2016-03-01-a-ubuntu14.04.tar.gz
tar xzvf swift-DEVELOPMENT-SNAPSHOT-2016-03-01-a-ubuntu14.04.tar.gz
fi
echo "export PATH=/home/vagrant/swift-DEVELOPMENT-SNAPSHOT-2016-03-01-a-ubuntu14.04/usr/bin:$PATH" >> ~/.profile
source ~/.profile
swift --version
sudo chown vagrant:vagrant /srv/hello-world
SHELL
end
Defining our Dependencies
In the Package.swift
file, specify our package name & dependencies:
import PackageDescription
let package = Package(
name: "HelloWorld",
dependencies: [
.Package(url: "https://github.com/nestproject/Frank.git", majorVersion: 0, minor: 2),
.Package(url: "https://github.com/kylef/Stencil", majorVersion: 0),
.Package(url: "https://github.com/czechboy0/Jay.git", Version(0, 3, 5))
])
Note that we're locking our version of Jay to 0.3.5 so we don't get their latest changes for Swift 3 compatibility, because it won't compile with our chosen dev snapshot of Swift.
Writing a Simple Endpoint with Frank
Open up main.swift
and define your first endpoint:
import Frank
get { request in
return "Hello World"
}
We can then build & run our project:
$ swift build
...
$ .build/debug/HelloWorld
This will launch the project and start hosting the web application. Hit the URL from your host machine (using the port you specified in the Vagrantfile
. For me, this was http://localhost:8082
on my Mac.
Define Dynamic Templates with Stencil
To have dynamic HTML returned, we'll use Stencil. To render a template, we'll first define a helper method that will compile & render the template using some provided variables.
Create stencil.swift
and type the following:
import Stencil
import Inquiline
import Frank
import PathKit
func stencil(path: String, _ context: [String: Any] = [:]) -> ResponseConvertible {
do {
let template = try Template(path: Path("Resources") + Path(path))
let body = try template.render(Context(dictionary: context))
return Response(.Ok, headers: [("Content-Type", "text/html")], body: body)
} catch {
print("error reading template: \(path)")
return Response(.InternalServerError)
}
}
Here we try to load a template from he Resources
directory (relative to our project's root). If it is not found (or if we have an error in the template itself, we'll get an error. We must catch this error and render a proper HTTP response (500 in this case). If we don't, a simple error will crash the web server!
With this in place, we can go back to main.swift
and add a new endpoint:
get("users", *) { (request, username: String) in
stencil("index.stencil", ["username": username])
}
Here we take an argument in the URL and pass that off to our template. The template looks like:
<h1>Hello {{username}}</h1>
If we build & run the application now we can see our changes. Open up http://localhost:8082/users/ben
on your host machine and see the result.
Rendering JSON
To create an API we'll usually be returning JSON data. Define a new helper in render_json.swift
:
import Frank
import Foundation
import Inquiline
import Jay
func renderJSON(object: Any, status: Status = .Ok) -> ResponseConvertible {
do {
let headers = [("Content-Type", "application/json")]
let jsonData = try Jay().dataFromJson(object)
let json = try jsonData.string()
return Response(status, headers: headers, body: json)
} catch {
print("Error serializing \(object) to JSON")
return Response(.InternalServerError)
}
}
We can then use this in our endpoint definition like so:
import Foundation
get("api") { request in
return renderJSON(["date": "\(NSDate())"])
}
The if we build the project, restart the server, and visit /api
in the browser, we'll see today's date returned as a JSON response.