Getting Started with Swift on Server
Tags: swift, beginners, cloudnative
Published: 2021-12-03 19:47:59

We've been working with Node.js for over 10 years now and decided to pick up a new challenge. For "strong typing" features, people are exploring [Rust](https://www.rust-lang.org) and [Go](https://golang.org/) as alternatives to Node.js with TypeScript. Swift on Server has been around for a few years now, and Swift is an interesting language to learn for mobile development. It feels quite similar to JavaScript with the new async/await features and is easily readable.

There are some interesting http frameworks (think Express.js, Fastify, etc.) to simplify working with `SwiftNIO` such as [Vapor](https://vapor.codes/), [Hummingbird](https://github.com/hummingbird-project) and [Perfect](https://www.perfect.org/). There's even a [full CMS (Feather)](https://github.com/FeatherCMS/feather).

The [Swift Server Work Group](https://www.swift.org/sswg/) names a few [benefits on why Swift is great](https://www.swift.org/server/#why-swift-on-server) as a server-side language:

- Small footprint
- Quick startup time
- Deterministic performance

> Small footprint: One of the main goals of a modern cloud platform is to maximize resource utilization by efficiently packing services into a single machine. Cloud services built with Swift have a small memory footprint (measured in MB)–especially when compared to other popular server languages with automatic memory management. Services built with Swift are also CPU-efficient, given the language’s focus on performance.

> Quick startup time: Swift-based applications start quickly since there are almost no warm up operations. This makes Swift a great fit for cloud services, which are often re-scheduled onto new VMs or containers to address platform formation changes. Using Swift also helps streamline continuous delivery pipelines, since you incur less wait time for new versions of the service fleet to come online. Finally, quick boot times make Swift a perfect fit for serveless applications such as Cloud Functions or Lambda with negligible cold start times.

> Deterministic performance: Swift’s use of ARC (instead of tracing garbage collection) and its lack of JIT gives it an important edge in the cloud services space. While tracing garbage collection technologies have vastly improved in the last few years, they still compete with the application for resources which triggers non-deterministic performance. The absence of JIT means no runtime optimization or de-optimization. It’s challenging to debug non-deterministic performance, and language-induced non-deterministic performance can both confuse and mask application-level performance issues that could otherwise be addressed.

Those all sound great, so we wanted to see if we can use modern, Cloud Native tools to build and deploy a Swift Server application. But first we needed to build one. We decided to go with Vapor and [PlanetScale](https://planetscale.com), to add some complexity to the regular "Hello World" app.

## Install

### Swift and Vapor

On macOS, if you have the Developer Tools and Xcode installed, you should be good to go already. You can check if Swift is properly installed:

```bash
  » swift --version
swift-driver version: 1.26.9 Apple Swift version 5.5.1 (swiftlang-1300.0.31.4 clang-1300.0.29.6)
Target: arm64-apple-macosx12.0
```

If you don't have Swift installed yet, follow the [Installing Swift](https://www.swift.org/getting-started/#installing-swift) guide for macOS, Linux or Windows. You'll also need [Vapor](https://docs.vapor.codes/4.0/install/macos/), which you can easily install with brew:

```bash
brew install vapor
```

### PlanetScale

Install the [PlanetScale CLI](https://docs.planetscale.com/reference/planetscale-environment-setup). For macOS, via brew:

```bash
brew install planetscale/tap/pscale mysql-client
```

[Full PlanetScale CLI reference](https://docs.planetscale.com/reference/planetscale-cli)

## Creating your first Swift on Server / Vapor project

This is as simple as `npm init`:

```bash
vapor new swift-api-demo
```

To go beyond the hello world steps, let's **install Fluent** for MySQL and **skip Leaf** during the project initialization.

```bash
cd swift-api-demo
open Package.swift
```

This will open Xcode and download/install vapor dependencies. You should see the following screen:

![screenshot of Xcode with the initial scaffolding of a new project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1getbxttsfdp509cz7r7.png)
 
Now simply hit the "Run" button (▶︎, or `⌘R`). In the Xcode terminal you should see a notice that the server has started (`[ NOTICE ] Server starting on http://127.0.0.1:8080 `). And voilà, you can curl your server instance:

     » curl localhost:8080
    It works!%
     » curl localhost:8080/hello
    Hello, world!%

### VScode

Instead of XCode, you can also write Swift applications in VSCode. There is a [vscode extension](https://github.com/swift-server/vscode-swift) available for the language support. We love "auto format on save", which is a little tricky, but can be achieved by downloading and compiling [swift-format](https://github.com/apple/swift-format#command-line-usage):

```bash
VERSION=0.50500.0  # replace this with the version you need
git clone https://github.com/apple/swift-format.git
cd swift-format
git checkout "tags/$VERSION"
swift build -c release
ln -s /usr/local/bin/swift-format .build/release/swift-format
```

The last line will create a symlink in your binaries folder (macOS specific, you may need to use a different path). Afterwards, `format on save` should work wth the [SwiftFormat extension](https://marketplace.visualstudio.com/items?itemName=vknabel.vscode-swiftformat).

## Create the PlanetScale Database

After installing the [PlanetScale CLI](https://github.com/planetscale/cli) you need to log in, create a database and a password:

```bash
    pscale auth login
    pscale database create swift-server-db
    pscale password create swift-server-db main production-password
```

![screenshot of the promote to production screen on planetscale](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xpt30l7phnreb7o6ssy6.png)

The default database has no "production" branch, which means schema changes can be applied directly on `main`. Once you _promote_ your branch to production, you need to follow a "feature branch flow", just like in Git:

Create a branch, then open a SQL shell to create a new table:

```bash
    pscale branch create swift-server-db todos
    pscale shell swift-server-db todos
```

We're creating the table `todos` with an autogenerated id and a title:

```sql
CREATE TABLE `todos` (
  `id` INT AUTO_INCREMENT PRIMARY KEY,
  `title` varchar(1024) NOT NULL,
  `created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
  `updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
exit;
```

These changes are now in a branch. You can open a Deploy Request and merge it into `main`:

```bash
pscale deploy-request create swift-server-db todos
pscale deploy-request deploy swift-server-db 1
```

If you want to use [DBeaver](https://dbeaver.io/) or any other MySQL UI, you can connect to the branch directly with `pscale connect <db-name> <branch-name>`.

## Connecting the Pieces

### Identifiers (IDs)

Vapor/Fluent work with `UUID`s as default. MySQL doesn't have native support for those, so it's recommended to switch to _Integer_ IDs:

_Sources/App/Models/Todo.swift_:

```swift
import Fluent
import Vapor

final class Todo: Model, Content {
    static let schema = "todos"

    @ID(custom: "id")
    var id: Int?

    @Field(key: "title")
    var title: String

    init() { }

    init(id: Int? = nil, title: String) {
        self.id = id
        self.title = title
    }
}
```

### Vapor with PlanetScale

After creating a password on PlanetScale, you should be able to [Connect securely](https://docs.planetscale.com/reference/secure-connections) to your database. You can find the credentials (host, user, password, db name) on your Dashboard:

![planetscale connection settings](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j5jndt0x8hfic18v9ero.png)
 
The default database has no "production" branch, which means schema changes can be applied directly on `main`. Once you _promote_ your branch to production, you need to follow a "feature branch flow", just like in Git:

Create a branch, then open a SQL shell to create a new table:

```bash
    pscale branch create swift-server-db todos
    pscale shell swift-server-db todos
```

We're creating the table `todos` with an autogenerated id and a title:

```sql
CREATE TABLE `todos` (
  `id` INT AUTO_INCREMENT PRIMARY KEY,
  `title` varchar(1024) NOT NULL,
  `created_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6),
  `updated_at` datetime(6) NOT NULL DEFAULT CURRENT_TIMESTAMP(6)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
exit;
```

These changes are now in a branch. You can open a Deploy Request and merge it into `main`:

```bash
pscale deploy-request create swift-server-db todos
pscale deploy-request deploy swift-server-db 1
```

If you want to use [DBeaver](https://dbeaver.io/) or any other MySQL UI, you can connect to the branch directly with `pscale connect <db-name> <branch-name>`.

## Connecting the Pieces

### Identifiers (IDs)

Vapor/Fluent work with `UUID`s as default. MySQL doesn't have native support for those, so it's recommended to switch to _Integer_ IDs:

_Sources/App/Models/Todo.swift_:

```swift
import Fluent
import Vapor

final class Todo: Model, Content {
    static let schema = "todos"

    @ID(custom: "id")
    var id: Int?

    @Field(key: "title")
    var title: String

    init() { }

    init(id: Int? = nil, title: String) {
        self.id = id
        self.title = title
    }
}
```

### Vapor with PlanetScale

After creating a password on PlanetScale, you should be able to [Connect securely](https://docs.planetscale.com/reference/secure-connections) to your database. You can find the credentials (host, user, password, db name) on your Dashboard:

![xcode console log without errors](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w4gtgrmaegdlhavchioz.png)

## Wrapping it up

Overall we're pretty excited how things have developed in the _Swift on the Server_ world and how [Vapor](https://vapor.codes) works. There are some really cool features coming up in Swift 5.6 and everything is under active development. The [Vapor Community on Discord](http://vapor.team) is super friendly and helpful!

As a Proof of Concept, we're able to run a http service and connect to a MySQL database. We can store and retrieve data for a simple todo list. That's all pretty nice, but it's still just "localhost" development. We wanted to know how to get this up into the cloud, so we wrote another article on [how to deploy your swift api to the cloud](https://zentered.co/articles/deploying-swift-api-on-google-cloud/). TL;DR: this is pretty simple and we were able to use Cloud Native tooling such as Cloud Build and Cloud Run to automate the entire deployment pipeline from GitHub to Production.

You can find the source code here: [Swift API Demo on GitHub](https://github.com/zentered/swift-vapor-demo). If you have any questions or comments, please [reach out on Twitter](https://twitter.com/zenteredco) or [start a discussion on GitHub](https://github.com/zentered/zentered/discussions/new?category=ideas-feedback).

## TODO

A few things we need to do some more research on:

- Write tests and implement a TDD workflow
- How can we run Vapor Migrations on PlanetScale?
- How can we use/automate semver tags with XCode?

## Further Reading

- [Swift on Server](https://www.swift.org/server/)
- [Generic Docs for Vapor](https://docs.vapor.codes/4.0/)
- [Fluent Docs for databases](https://docs.vapor.codes/4.0/fluent/overview/)
- [Authentication](https://docs.vapor.codes/4.0/authentication/)
- [Vapor Chat](http://vapor.team)