Server-side Swift opens exciting new worlds to Swift developers. Best of breed frameworks like Kitura and Vapor expose the full power of Swift, while adding compelling features and API’s. Being able to develop end to end with full stack Swift can be a game-changing revelation.
However, there are subtle but significant differences between Swift for Client-side apps, like iOS or watchOS apps, and Swift for Server-side web environments.
Swift remains a constant North Star so many of these differences are easy to overlook. That’s a good thing. But they are worth understanding.
You can do a lot of productive Server-side Swift development without understanding these differences. But, learning to recognize them and think in Server-side Swift can greatly improve your productivity.
In this post, you’ll explore of some of the key differences between Client-side and Server-side Swift. You’ll also learn how to use these to take advantage of Swift in its Server-side form. Time to dive in!
To kick things off, start by downloading the materials for this tutorial (you can find a link at the top or bottom of this tutorial). The sample app is a simple Server-side Swift app called Copernicus. Note that everything discussed here applies universally to all Server-side Swift frameworks. I’ve used Kitura because it exposes raw Server-side Swift commands especially well.
Take the sample app out for a test flight. Start by opening the project in Xcode, choose
Build>Run to quickly build and run it, and…
But wait- you don’t have an Xcode project yet, so even these most basic first steps don’t work here!
Like most Server-side projects and repos, the sample project files don’t come with an Xcode project. It also lacks the binary dependencies it’ll need to build and run. Before you can do anything in Xcode, you’ll need to drop down into Terminal to prepare things. You’ll do that now.
Open Terminal and run
cd into the sample project directory. Then use
ls -la to list the directory’s full contents.
-rw-r--r--@ 1 brian staff 417 Jun 10 15:49 Package.swift -rw-r--r--@ 1 brian staff 45 Jun 10 15:45 README.md drwxr-xr-x@ 3 brian staff 96 Jun 10 15:45 Sources drwxr-xr-x@ 4 brian staff 128 Jun 10 15:45 Tests
As you can see, your Server-side Swift project isn’t very… there yet. But the seed of the project is present in it’s
Package.swift file and our
./Tests directories. Run
cat Package.swift to view the simple dependencies:
// swift-tools-version:5.0 import PackageDescription let package = Package( name: "Copernicus", dependencies: [ .package(url: "https://github.com/IBM-Swift/Kitura.git", .upToNextMajor(from: "2.5.0")), ], targets: [ .target( name: "Copernicus", dependencies: ["Kitura"]), .testTarget( name: "CopernicusTests", dependencies: ["Copernicus", "Kitura"]), ] )
As you can see, the project has a single dependency on the
Kitura framework. To retrieve and assemble this, run:
swift build ls -la
You’ll see something similar to the following:
drwxr-x---@ 6 brian staff 192 Jun 10 15:57 .build -rw-r--r--@ 1 brian staff 2525 Jun 10 15:57 Package.resolved -rw-r--r--@ 1 brian staff 417 Jun 10 15:49 Package.swift -rw-r--r--@ 1 brian staff 45 Jun 10 15:45 README.md drwxr-xr-x@ 3 brian staff 96 Jun 10 15:45 Sources drwxr-xr-x@ 4 brian staff 128 Jun 10 15:45 Tests
Here’s what just happened:
Next, it recursively checked each dependency for any potential sub-dependencies until all needed assets were cached locally in
../.build/repositories. It then stored the versions of each retrieved dependency in a newly-created
swift buildfirst retrieved the repository of Kitura itself. Next, it retrieved the repos of
Kitura'sdependencies. It based this on its
- Finally, it transformed these repos into a clean set of project assets for each dependency in
To get a sense of how these assets assemble, run
open .build. Take a quick peek at the
.build folder structure in Finder:
OK, back to Terminal. You’ve retrieved the assets needed, so now you need to build and configure an Xcode project to house them. Do this with the following commands:
swift package generate-xcodeproj ls -la
When completed, you’ll see a new
Copernicus.xcodeproj Xcode project file:
drwxr-x---@ 6 brian staff 192 Jun 10 15:57 .build drwxr-xr-x@ 16 brian staff 512 Jun 10 15:58 Copernicus.xcodeproj -rw-r--r--@ 1 brian staff 2525 Jun 10 15:57 Package.resolved -rw-r--r--@ 1 brian staff 417 Jun 10 15:49 Package.swift -rw-r--r--@ 1 brian staff 45 Jun 10 15:45 README.md drwxr-xr-x@ 3 brian staff 96 Jun 10 15:45 Sources drwxr-xr-x@ 4 brian staff 128 Jun 10 15:45 Tests
With this initial work completed, you can now open the sample project. Do this now. A handy Terminal shortcut to do this is the command
In Xcode, build and run your project as you normally would. Swift’s Command Line Tools have assembled the completed project and assets. So, everything should build and run cleanly.
When Copernicus gets off the ground, you should see the following:
Next, you’ll see the differences between the client and server side worlds. You’ll find key differences between the two Swift-y worlds. These in turn will aid you in your quest to think more clearly in Server-side Swift.
An Expanded Universe
The single most important differences between Client-side and Server-side Swift projects are the platforms they target.
Client-side Swift apps exclusively target Apple-native hardware platforms. The entire universe of Client-side apps lives within the conceptual box of Apple products. This means all Client-side project dependencies, whether added manually or via tools like
CocoaPods, are ultimately binaries targeted and compiled directly for the appropriate Apple hardware platform.
The universe of Server-side Swift is considerably larger. Everything must by definition work not only on macOS, but also on Ubuntu Linux, Docker and the cloud.
This expanded world requires significant architectural changes. It also requires some subtle changes in how you think about your projects. Specifically, you must use tools and build frameworks that work across all supported environments.
This means that when you’re developing Server-side, even project dependency must be able to be downloaded and built on the target platform’s native using native toolsets. More specifically, every dependency must be registered and built by the cross-platform Swift Package Manager, via the instructions in
If you added Apple platform-compiled binaries to a Server-side project using
CocoaPods, they would work within the confines of Apple-branded hardware. But you couldn’t use those same dependencies when you deployed your project to Linux, Docker or the cloud. Your project would not build.