Running Swift on Linux is intriguing because it allows us to create web applications with Swift and host them on an inexpensive Linux VPS. In this episode we'll use Vagrant to create an Ubuntu virtual machine, install a working version of the Swift development snapshot, and write a tiny web application, complete with routing, parameter extraction, template rendering with Stencil, and JSON parsing. You'll learn about Swift Build, specifying version dependencies, and where to look for the source code.
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.