Introducing our server project

Let's go out with a bang! For this last chapter, we are going to create a small project to test developing a server app in Swift. We will use the IBM Swift Package Catalog to find a web server framework. I really like using Slack for team communication. If you haven't tried it, you should consider evaluating whether it could be a good tool for your team. One of the powerful features of Slack is the array of integration options you have to customize the experience for your team. Slack has opened many of its APIs to developers for customization and integration. Slack even provides an App Store for users to add third-party apps that their teams can use together. The catch, if there is one, is that your third-party app or integration has to be hosted on an outside server. We are going to create a Slack integration that you can later modify into a full Slack app of your own. Our Slack integration will be written entirely in Swift, and it could be hosted on a Linux virtual machine in the cloud with a provider such as Heroku, Digital Ocean, or Amazon Web Services.

Our project needs a web server framework to work properly. We could write one from scratch, or we could see what third-party frameworks are available. Using the IBM Swift Package Catalog, I found several web application frameworks that are highly rated and are both actively developed and popular with developers. IBM's Kitura, Perfect by PerfectlySoft, and Vapor by Vapor are all viable candidates from which to choose. Each of these projects will feel familiar to you if you have ever dabbled with Node.js and Express, or with Ruby on Rails. While any of these frameworks would work for our project, I selected the Vapor project for our app because it was at the top of the list in the "essential" category at the time I was writing this chapter.

According to Vapor, their project is the most used web framework written for Swift. You can learn more about Vapor, including the Swift version it supports and links to the documentation, at https://swiftpkgs.ng.bluemix.net/package/vapor/vapor.

Now that we have covered the libraries and frameworks that we will use, let me give you a description of the server application that we will be building together. Slack gives developers the option to create custom integrations for their own team or to make Slack apps that would be available to any team. We are going to create a custom integration for a single team. However, you could easily convert our customization into a full app for any team to discover.

We are going to build a storefront web application to sell widgets. As a user purchases a widget from us, we will process the order and send the order to our order-tracking channel in Slack. In the interest of keeping this application simple, we are going to take some shortcuts.

Since you learned how to install Swift in Chapter 2, Discovering New Territories – Linux at Last! we will skip over that step and move on to installing the Vapor framework. We are going to add Vapor's command line toolkit to gain access to shortcut commands and assistance for common tasks.

Here's how you do it:

To install the toolkit, run the following command in a terminal:

curl -sL toolbox.vapor.sh | bash

You can verify that the command worked by running:

vapor -help

Next, let's create a new Vapor project and name it storefront:

vapor new storefront

Our newly created project will have the following file structure:

Setting up our environment and project

The file structure should be familiar to you, as it follows the structure required for the Swift Package Manager to work. Under the cover, vapor creates a new project with swift package init--type executable. The Vapor script also adds the vapor framework as a dependency in Package.swift. Our main.swift is our entry point, since we created an executable program.

I'm going to develop my code on a Mac and then deploy it to a Linux VM in the cloud. For me, the benefit is that I can use Xcode and it's debugging tools for my local development. In fact, the Vapor framework supports this concept by offering a command in their toolkit to generate an Xcode project for development. Let's create an Xcode project that we can use for development of our storefront app:

vapor xcode 

When Vapor creates a companion Xcode project, it first checks to see if you are missing any dependencies specified in the Package.swift file. Vapor will download any missing dependencies for you, prior to creating the Xcode Project. In addition, Vapor will create a scheme to use for running your app in Xcode. Finally, Vapor will show you which toolchain the Xcode project expects to be linked against:

$vapor xcode 
No Packages folder, fetch may take a while... 
Fetching Dependencies [Done] 
Generating Xcode Project [Done] 
Select the `App` scheme to run. 
Make sure Xcode > Toolchains > 3.0-GM-CANDIDATE is selected. 
Open Xcode project? 
y/n>nz 

Whenever you create new dependencies for your project, you have to rebuild the project so that the Swift Package Manager can download your new dependency before attempting to compile your code:

vapor clean or vapor build --clean 

Let's take a quick look at Package.swift to see what the Vapor create command generated for us. We can configure the name of the app with something that better suits our project. The current default name is VaporApp, but we could change this to Storefront. You should also notice that the vapor framework is added for us as a dependency.

import PackageDescription 
 
let package = Package( 
    name: "VaporApp", 
    dependencies: [ 
        .Package(url: "https://github.com/vapor/vapor.git", majorVersion: 0, minor: 18) 
], 
    exclude: [ 
        "Config", 
        "Database", 
        "Localization", 
        "Public", 
        "Resources", 
        "Tests", 
    ] 
) 

When you use the Vapor CLI to create a new project, Vapor adds example code with documentation to project. Open main.swift and glance over the included routes and comments. Delete everything in this file, and we will be build our app from scratch.

Now that we have a Droplet instance, we need to talk about routing. Routing is an essential function for every web framework. When an incoming request is received, we need to have a way to appropriately filter and handle each request. Vapor gives you multiple options for addressing your routing concerns. We are going to create two routes for our application: one to serve our shop page, and the other to respond to post requests when a user purchases an item on our page.

A basic route in Vapor is composed of a method, path, and closure. Two of our routes fall into this category. Vapor routing supports the standard RESTful HTTP methods (get, post, put, patch, delete, and options). We register routes by calling the corresponding method on our Droplet instance, passing in our route path and returning a closure we define.

drop.get("/") { request in 
    return try drop.view.make("shop.html") 
} 
 
drop.post("purchase") { request in 
 
// more stuff happening here but omitted 
 
var response = try Response(status: .ok, json: json) 
    return response 
} 

Our first route handles all get requests for the root directory of our website. When this route is requested, we return the shop.html view. Our second route handles post requests for the /purchase route. Once we finish carrying out work, we return a response to the requester with a status and JSON payload.

Vapor also supports nested routes and parameters. Creating a nested route is as easy as replacing the forward slashes in the URL with commas when registering your route.

// Nested route 
drop.get("products", "vehicles", "trucks") { request in 
    return "You requested /products/vehicles/trucks" 
} 

Vapor handles parameters by making them type safe. Many web frameworks default to using strings for route parameters and types, which can be error prone. Using Swift's closures allows for a safer way to access route parameters. In the following example, we define the route to accept an Int parameter. Our route matches artboard/:id where our :id parameter must be an Integer value.

// Type Safe parameters 
drop.get("artboard", Int.self) { request, productId in 
    return "You requested Artboard #\(productId)" 
} 

We could have also written this without using route parameters and then access our parameters on the request object.

drop.get("artboard", ":id") { request in 
    guard let productId = request.parameters["id"]?.int else { 
        throw Abort.badRequest 
    } 
     
    return "You requested Artboard #\(productId)" 
} 

When we serve the root level document on our application, we return a shop.html view. Our simple page displays a welcome message and details of three products.

Defining our shop view

When a user clicks the buy now button, we execute a jQuery Ajax post command to communicate with our server. We send the product ID of the product we want to purchase to our "/purchase" route.

On the server, when we receive a request that matches this route, we extract the product ID and search for a matching product in our local store. Of course, in a production app, we would use a database to house our products and even populate our store listings. In situations where we can't find a valid product ID on our request object or where we can't find a matching product for a supplied product ID, we throw an error that is sent back to the client.

Finally, we create a JSON payload that contains some of our product details and return it to the client with a successful status code.

drop.post("purchase") { request in 
    drop.log.info("purchase request made") 
    guard let product_id = request.data["product_id"]?.int else { 
        throw Abort.badRequest 
    } 
     
    guard let product = products.filter({ (prod) -> Bool in 
        return prod.id == product_id 
    }).first else{ 
        throw Abort.badRequest 
    } 
    
 
    let json = try JSON(node: [ 
        "Product" : "\(product.name)", 
        "price" : "\(product.price)", 
        ]) 
 
// more work happening and omitted  
 
    var response = try Response(status: .ok, json: json) 
     
    return response 
} 

When our client receives the post response, we display an alert dialog that thanks the user for their purchase. We also display the returned JSON data in a console.