/vapor

Elegant web framework for Swift that works on iOS, OS X, and Ubuntu.

Primary LanguageSwiftMIT LicenseMIT

Vapor

Vapor

A Laravel/Lumen Inspired Web Framework for Swift that works on iOS, OS X, and Ubuntu.

  • Insanely fast
  • Beautiful syntax
  • Type safe

Badges

Build Status Issue Stats PRs Welcome Slack Status

Getting Started

Clone the Example project to start making your application or check out the live demo running on Ubuntu. This repository is for the framework module.

You can also download the alpha Vapor Installer, which allows you to create a new project at the command line e.g. vapor new MyProject

You must have Swift 2.2 or later installed. You can learn more about Swift 2.2 at Swift.org

Want to make a pull request? You can learn how from this free series How to Contribute to an Open Source Project on GitHub

Work in Progress

This is a work in progress, so don't rely on this for anything important. And pull requests are welcome!

Wiki

Visit the Vapor Wiki for extensive documentation on using and contributing to Vapor.

Server

Starting the server takes two lines.

main.swift

import Vapor

let server = Server()
server.run()

You can also choose which port the server runs on.

server.run(port: 8080)

If you are having trouble connecting, make sure your ports are open. Check out apt-get ufw for simple port management.

Routing

Routing in Vapor is simple and very similar to Laravel.

main.swift

Route.get("welcome") { request in
	return "Hello"
}

//...start server

Here we will respond to all requests to http://example.com/welcome with the string "Hello".

JSON

Responding with JSON is easy.

Route.get("version") { request in
	return ["version": "1.0"]
}

This responds to all requests to http://example.com/version with the JSON dictionary {"version": "1.0"} and Content-Type: application/json.

Views

You can also respond with HTML pages.

Route.get("/") { request in
	return View(path: "index.html")
}

Or Stencil templates.

index.stencil

<html>
	<h1>{{ message }}</h1>
</html>
Route.get("/") { request in
	return View(path: "index.stencil", context: ["message": "Hello"])
}

If you have VaporStencil added, just put the View file in the Resources folder at the root of your project and it will be served.

Stencil

To add VaporStencil, add the following package to your Package.swift.

Package.swift

.Package(url: "https://github.com/qutheory/vapor-stencil.git", majorVersion: 0)

Then set the StencilRenderer() on your View.renderers for whatever file extensions you would like to be rendered as Stencil templates.

main.swift

import VaporStencil

//set the stencil renderer
//for all .stencil files
View.renderers[".stencil"] = StencilRenderer()

Response

A manual response can be returned if you want to set something like cookies.

Route.get("cookie") { request in
	let response = Response(status: .OK, text: "Cookie was set")
	response.cookies["test"] = "123"
	return response
}

The Status enum above (.OK) can be one of the following.

public enum Status {
    case OK, Created, Accepted
    case MovedPermanently
    case BadRequest, Unauthorized, Forbidden, NotFound
    case ServerError
    case Unknown
    case Custom(Int)
}

Or something custom.

let status: Status = .Custom(420) //https://dev.twitter.com/overview/api/response-codes

Public

All files put in the Public folder at the root of your project will be available at the root of your domain. This is a great place to put your assets (.css, .js, .png, etc).

Request

Every route call gets passed a Request object. This can be used to grab query and path parameters.

This is a list of the properties available on the request object.

let method: Method
var parameters: [String: String] //URL parameters like id in user/:id
var data: [String: String] //GET or POST data
var cookies: [String: String]
var session: Session

Session

Sessions will be kept track of using the vapor-session cookie. The default (and currently only) session driver is .Memory.

if let name = request.session.data["name"] {
	//name was in session
}

//store name in session
request.session.data["name"] = "Vapor"

Database

Vapor was designed alongside Fluent, an Eloquent inspired ORM that empowers simple and expressive database management.

import Fluent

if let user = User.find(5) {
    print("Found \(user.name)")

    user.name = "New Name"
    user.save()
}

Underlying Fluent is a powerful Query builder.

let user = Query<User>().filter("id", notIn: [1, 2, 3]).filter("age", .GreaterThan, 21).first

Controllers

Controllers are great for keeping your code organized. Route directives can take whole controllers or controller methods as arguments instead of closures.

main.swift

Route.get("heartbeat", closure: HeartbeatController().index)

To pass a function name as a closure like above, the closure must have the function signature

func index(request: Request) -> ResponseConvertible

Here is an example of a controller for returning an API heartbeat.

HearbeatController.swift

import Vapor

class HeartbeatController: Controller {

	override func index(request: Request) -> AnyObject {
		return ["lub": "dub"]
	}

}

Here the HeartbeatControllers's index method will be called when http://example.com/heartbeat/alternate is visited.

Resource Controllers

Resource controllers take advantage of CRUD-like index, show, store, update, destroy methods to make setting up REST APIs easy.

Single Resources

Route.resource("user", controller: UserController())

This will create the appropriate GET, POST, DELETE, etc methods for individual and groups of users:

  • .Get /user - an index of users
  • .Get /user/:id - a single user etc

Nested Resources

You can also create nested resources for one to many relationships. For example, a "company" can have multiple "users". This can be achieved by using dot notation in the path, as follows:

Route.resource("company.user", controller: CompanyUserController())

This will create appropriate nested GET, POST, DELETE, etc methods, for example:

  • .Get /company/:company_id/user - an index of users at a specific company
  • .Get /company/:company_id/user/:id - a specific user at a specific company

You can now access these parameters in a controller, as follows:

let companyId = request.parameters["company_id"]
let userId = request.parameters["id"] //Note: The final parameter is always `id`.

Middleware

Create a class conforming to Middleware to hook into server requests and responses. Append your classes to the server.middleware array in the order you want them to run..

class MyMiddleware: Middleware {
    func handle(handler: Request -> Response) -> (Request -> Response) {
        return { request in
            print("Incoming request from \(request.address)")

            let response = handler(request)

            print("Responding with status \(response.status)")

            return response
        }
    }
}

server.middleware.append(MyMiddleware())

Async

Use the AsyncResponse to send custom, asynchronous responses. You have full control over the response here, meaning you are responsible for writing all required headers and releasing the Socket when done. (Thanks @elliottminns)

Route.get("async") { request in
	return AsyncResponse() { socket in
		try socket.writeUTF8("HTTP/1.1 200 OK\r\n")
		try socket.writeUTF8("Content-Type: application/json\r\n\r\n")
		try socket.writeUTF8("{\"hello\": \"world\"}")

		socket.release()
	}
}

Hash

Vapor currently supports SHA1 hashes.

let hello = Hash.make("world")

For added security, set a custom applicationKey on the Hash class.

Hash.applicationKey = "my-secret-key"

Deploying

Vapor has been successfully tested on Ubuntu 14.04 LTS (DigitalOcean) and Ubuntu 15.10 (VirtualBox).

DigitalOcean

To deploy to DigitalOcean, simply

  • Install Swift 2.2
    • wget the .tar.gz from Apple
    • Set the export PATH in your ~/.bashrc
    • (you may need to install binutils as well if you see ar not found)
  • Clone your fork of the vapor-example repository to the server
  • cd into the repository
    • Run swift build
    • Run .build/debug/MyApp
    • (you may need to run as sudo to use certain ports)
    • (you may need to install ufw to set appropriate ports)

Upstart

To start your Vapor site automatically when the server is booted, add this file to your server.

/etc/init/vapor-example.conf

description "Vapor Example"

start on startup

exec /home/<user_name>/vapor-example/.build/release/VaporApp --workDir=/home/<user_name>/vapor-example

You additionally have access to the following commands for starting and stopping your server.

sudo stop vapor-example
sudo start vapor-example

The following script is useful for upgrading your website.

git pull
swift build --configuration release
sudo stop vapor-example
sudo start vapor-example

Heroku

To deploy on Heroku, one can use Kyle Fuller's Heroku buildpack which works out of the box with the vapor-example.

My website http://tanner.xyz is currently running using Vapor.

Attributions

This project is based on Swifter by Damian Kołakowski. It uses compatibility code from NSLinux by johnno1962.

Go checkout and star their repos.