Running a Web Server on iOS with Vapor

With Vapor, your iOS app can be both the client and the server to control your data — or even other devices. This tutorial will show you how to get started with client-server communication in the same process. By Beau Nouvelle.

5 (4) · 3 Reviews

Download materials
Save for later
Share

Most of the world’s websites run on machines in large server farms. If you’re an enthusiast, you might even have a few servers around your home to control things like your 3D printer or as file storage for your home theater.

In this tutorial, you’re going to break the rules and turn your mobile phone into a mobile server. The device in your pocket wasn’t designed for such a task, but when has that stopped you?

You’ll learn how to:

  • Integrate a Vapor server into your iOS Apps.
  • Connect to your iOS app from anywhere on the internet.
  • Update the state and contents of your app through a web browser.

By the end of this tutorial, you’ll have an iOS app that can host files and serve them up to anyone in the world!

Getting Started

Download the starter project by clicking the Download Materials button at the top or bottom of the tutorial.

You’re going to focus on integrating server-side code into an iOS app. The starter project comes with the required HTML for running the website you’ll be serving and some code for previewing files once uploaded.

Open the starter project in Xcode, take a look through the provided files and give it a run to make sure everything compiles.

Setting Up Your App’s Server

This project will use only two Swift Packages: Vapor and Leaf. The core Vapor package has everything you need for building the backend to your server, and the Leaf package allows you to create a webpage for the frontend.

After opening Xcode, SwiftPM should resolve and download the latest versions of these packages for you.

Examining Your Server

Before going any further, have a look at the structure of the project. When building multiplatform applications, developers generally divide a project into different directories based on the platforms they’re targeting.

Even though this project has a single target, the iOS frontend and server backend are separate. This is a great approach to take because it keeps the project tidy, and it reduces cognitive load during development by allowing you to focus on one area at a time.

Create a new file inside the server folder, name it FileServer.swift and add the following code:

import Leaf
import Vapor

class FileServer: ObservableObject {
  // 1
  var app: Application
  // 2
  let port: Int
  
  init(port: Int) {
    self.port = port
    // 3
    app = Application(.development)
    // 4
    configure(app)
  }
}

You’ll need more code before compiling, but here’s what the code above does:

  1. This handles the full lifecycle and configuration of the server. Everything you do on the server runs through Application.
  2. The port your server runs on. Generally, web servers run on port 80 or 443. However, these are reserved on iOS, so you’ll specify a different one later.
  3. This gives you more debug information during development. Be sure to change this to .production when you’re ready to ship!
  4. You’ll set this up next.

Configuring Your Server

Below the initializer, add a configure function.

private func configure(_ app: Application) {
  // 1
  app.http.server.configuration.hostname = "0.0.0.0"
  app.http.server.configuration.port = port

  // 2
  app.views.use(.leaf)
  // 3
  app.leaf.cache.isEnabled = app.environment.isRelease
  // 4
  app.leaf.configuration.rootDirectory = Bundle.main.bundlePath
  // 5
  app.routes.defaultMaxBodySize = "50MB"
}

Here’s what’s happening:

  1. Specifies how others can reach your server.
  2. Informs Vapor that you’ll be using Leaf as this project’s web rendering engine.
  3. Disabling web caching ensures you get the latest version of the webpage you’re working on any time you refresh it. This allows you to see your changes immediately. For production builds, caching can help improve the performance of your website.
  4. This setting tells Leaf where to find your .leaf templates, images, fonts, and other code for your server’s frontend.
  5. Vapor has a default file upload size of 16 KB. That’s far too small for the file server you’re building. Here, it’s set to 50 MB, but you can increase it if you plan to support larger files.

You must set the server’s hostname to “0.0.0.0” because this allows any other device on the same network to access your server. This network could be the one provided by your home router, or by the device itself — like an ad hoc configuration via a personal hotspot.

This also ensures the router can direct requests from outside the network to your device, allowing for connections over the internet.

Starting the Server

Before you can test the server, you’ll need a way to start it.

Below the configure function, add a new one and name it start().

func start() {
  // 1
  Task(priority: .background) {
    do {
      // 2
      try app.start()
    } catch {
      fatalError(error.localizedDescription)
    }
  }
}

There are two important things happening here:

  1. First, you run the server on a background thread.
  2. Then, start the server at 0.0.0.0 and the specified port.

Running the server at a lower priority ensures that you have the smoothest possible experience on both systems.

Note: Generally, when building a Server-Side Swift application you’ll want to run it on the Main Thread. But, this project also has an iOS component, and it’s essential the main thread is free to process user interactions. Otherwise, the iOS app may see a significant performance drop with stuttering frames any time the server is doing work.

Open ContentView.swift, initialize the server and call server.start() within .onAppear.

struct ContentView: View {
  @StateObject var server = FileServer(port: 8080)

  var body: some View {
    Text("Hello, World!")
      .onAppear {
        server.start()
      }
  }
}

Build and run your app. You’ll see “Hello, World!” in the middle of the simulator, but if you take a look at the console, you’ll see:

Server starting on http://0.0.0.0:8080

Navigate to localhost:8080 in a web browser on the same computer, and you’ll see the following error message:

{
   "error": true,
   "reason": "Not Found"
}

This is a response from the server informing you that it couldn’t find anything to serve you at that path.

This is expected because you haven’t created the routes yet. A great success nonetheless!