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:
swift buildfirst retrieved the repository of Kitura itself. Next, it retrieved the repos of
Kitura'sdependencies. It based this on its
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
- 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.
A New Center of Gravity
In more Copernican terms, Terminal and Swift Package Manager are the twin suns around which the Server-side worlds revolve. It’s possible to develop Client-side Swift projects without using either tool. But both are at the heart of every Server-side Swift project.
The corollary to this is that Xcode is not an essential part of Server-side Swift. This is a huge difference from Client-side Swift. Some Server-side Swift developers choose to omit Xcode and skip building an Xcode project altogether!
Now, if you love Xcode, don’t panic. :] Generating an Xcode project is the Server-side norm. Xcode is, and will remain, a great environment for developing Server-side Swift apps.
But Xcode can’t help you build or run Server-side apps once you switch from Apple devices to Linux, Docker and beyond. You can build, run and test your Server-side apps in Xcode as you’re developing on your mac. However, everything you build via Xcode will be a transient artifact to be discarded when you deploy these apps to Linux, Docker or the cloud.
In fact, you can take this one step further. The entire Xcode project in Server-side Swift is a transient byproduct of core processes controlled by Terminal and Swift Package Manager.
In practice, a common project workflow after creating new assets in Terminal is to run
swift package generate-xcodeproj to regenerate a completely new Xcode project file. Imagine doing that to an established Client-side Swift project!
You’ve touched on binaries and frameworks at several points so far. Time to take a look at how frameworks translate to the expanded Server-side universe.
Building a Common Foundation
Swift is a fantastic constant across heterogenous environments. But the Swift code you’ll write isn’t the same everywhere. This is because Swift isn’t an island.
When you write Swift code, you dip into helping frameworks such as
Foundation. This is important. It provides an array of critical primitives, types and functionality.
Additionally, its presence helps the Swift Standard Library stay small and highly focused. Simply put, without
Foundation, Swift wouldn’t be the Swift we know and love.
It can be jolting to realize that
Foundation doesn’t naturally exist everywhere. You shouldn’t take its presence for granted.
In fact, in the early days the lack of
Foundation on Linux was a great challenge. A team of relatively unsung heroes worked to implement much of
Foundation. This became The Foundation Project.
Thanks to the ongoing efforts of The Foundation Project, you can take for granted that
Foundation can do what you need across all Server-side Swift platforms. That said, not all parts of
Foundation have been implemented on other platforms. It’s always a good idea to check the project’s current Implementation Status page to see if the part of
Foundation you’re planning to use is present beyond Apple’s platforms.
Remember, when you take Swift beyond its Apple-native box, you should make sure all the frameworks you’d like to use will work.
Choose Wisely, Grasshopper
OK, it’s time for a quick confession:
In the early days of Server-side Swift, a foolish grasshopper needed a specific piece of Server-side functionality for a Server-side project. He blithely grabbed a nice-looking macOS/iOS framework from Github without much thought.
“Sure, it doesn’t have a
Package.swift file,” I thought. “But I’ll just throw in a simple
Package.swift file for it and be done. Am I clever!”
The grasshopper discovered that while you sometimes can to adapt a package for Server-side, that’s not always the case. This is especially true if a package has nested dependencies on platform-specific tools.
The grasshopper learned to respect the differences between Apple-only and heterogenous environments. He also learned to take this into account when making architectural decisions. Learn from the foolish, fellow Server-side grasshoppers. :]
Still, within any web framework there are cases where you need to reach beyond officially supported frameworks for a piece of functionality. So, how do you determine if a promising-looking framework will work Server-side? The choice comes down to a few simple questions:
- Does the framework have a valid
Package.swiftfile? If not, don’t pull a Brian®. Just say no and check out the another option. Otherwise, keep going.
- Does it contain any mac-specific dependencies, either via
CocoaPods, or nested directly within it? If so, again, get outta Dodge now! Otherwise, proceed, grasshopper.
- Are all primary and secondary dependencies also available on Linux? When it’s time to build your project beyond the Mac, will the Linux Terminal be able to retrieve them using
apt-get?Linux users can check this by running
apt-cache search <keyword>. They could also use via a package manager like Synaptic).
If a framework clears these hurdles, it’ll make the leap to compile beyond Apple platforms. Again, on the majority of server-side projects, this likely won’t be an issue. But a little understanding about what’s needed for a framework to go beyond Planet Apple goes a long way towards thinking in Server-side Swift.
A Copernican Revolution
The shift from Apple-controlled hardware and operating systems to a broader universe leads to several changes in architecting and building out Server-side Swift applications. Paradoxically, this might seem like either:
- No Big Deal: It’s just a few things to do differently. Otherwise, don’t worry about it.
- A Very Big Deal Indeed: If you really think about it, you’re in a whole different universe here.
So, which point of view is right? As you may have guessed, the answer is both. :]
The best way to understand the subtle differences between Swift’s Server- and Client-side worlds is with an analogy to The Copernican Revolution.
In Copernicus’s time, common sense knew that the earth was at the center of the cosmos. The sun, moon, planets and stars all rotated around the fixed foundation of the earth. This is like the Xcode- and Apple-controlled cosmos that Client-side apps have inhabited.
Of course, Copernicus realized that sun was at the center of the cosmos. Thinking differently opened new possibilities. It became easier to reason about and resolve long-standing problems.
Ultimately, new realms of discovery and exploration opened that simply weren’t possible before.
This is the perfect model for thinking in Server-side Swift. The expanded universe of Server-side Swift asks you to change the way you think about Swift and Xcode. In exchange, it opens new possibilities that can enhance your Swift skills and apps.
Like the original Copernican Revolution, nothing changes. And everything does.
Copernicus’ critics scoffed that his discovery changed nothing practical from day to day. People still went to bed, woke up and performed the same tasks they always had. Shifting the center of the cosmos from the earth to the sun changed nothing.
This is true in a post-Xcode-centric Swift world as well. Xcode still works when developing for Server-side. Other than a few small workflow changes, nothing looks much different on the surface.
But there are advantages to expanding your thinking to include changes brought by Server-side Swift. It gives you greater insight into why we do things differently when targeting the server.
You gain the simple but essential tools you need to leverage Terminal and Swift Package Manager. Similarly, it gives you the tools to choose and then implement frameworks on your Server-side projects.
Beyond these pragmatic benefits, it’s important to appreciate the work that went into
Kitura and any other framework designed to build and run cross-platform. You rely on that work every time you write a line of Swift that uses these platforms. It’s amazing that in a few years, Swift has grown from a proprietary Apple-only language to a cross-platform tool with a broad and growing scope!
One More Thing®
As I wrote this post, WWDC 2019 delivered several new developments. These included Xcode 11’s new integrated support for Swift Package Manager.
Conceptually, this doesn’t have any impact on what you’ve learned here. But practically, Xcode 11’s SPM integration promises some nice streamlining of practical workflows.
Here’s a couple of quick teasers:
First, in Xcode 11 beta you can now choose to create a new Swift Package project:
When I create a quick Package, I’m presented with a sample
Package.swift file. Even better, I’m presented with a new hierarchical visualization of the assets specified in the left-hand Project Navigator.
Now I’ll pop in my simple Kitura
Package.swift from the Copernicus sample project. It’s a revelation to watch Xcode 11 beta sequentially analyze its contents and recursively nested dependencies.
It then retrieves and assembles these frameworks. It even creates a
Package.resolved file on my behalf.
I won’t pursue this further here. But, I’m exciting to see how the deep integration of Swift Package Manager into Xcode 11 will impact real-world Server-side Swift workflows.
Where to Go From Here?
Thinking in Server-side Swift isn’t a big leap. Consider the platform’s breadth and think about what you’re asking your Swift code and tools to do. As a result, you’ll write better, more efficient code that works with the Server-side world.
What do you think? Is the expanded world of Server-side Swift, like much of the recent WWDC, a bit of a mind-blower? Or does the continuity of Swift make it feel more like business as usual from your perspective? Let us know in the comments, and happy Server-side Swifting!