Home iOS & Swift Books Server-Side Swift with Vapor

2
Hello, Vapor! Written by Tim Condon

Beginning a project using a new technology can be daunting. Vapor makes it easy to get started. It provides a handy command line tool to create a starter project for you.

In this chapter, you’ll start by installing the Vapor Toolbox, then use it to build and run your first project. You’ll finish by learning about routing, accepting data and returning JSON.

Vapor Toolbox

The Vapor Toolbox is a command line interface (CLI) tool you use when developing Vapor apps. It helps you create a new Vapor project from a template and can add dependencies as needed.

Before you can install the toolbox, you need to ensure your system has Swift installed. On macOS, simply install Xcode from the Mac App Store. On Linux, download it from https://www.swift.org as install as described below.

Vapor 4 requires Swift 5.2, both in Xcode and from the command line. Xcode 11.4 and 11.5 both provide Swift 5.2.

Installing on macOS

Vapor uses Homebrew to install the Toolbox.

If you don’t have Homebrew installed, visit https://brew.sh and run the installation command.

In Terminal, run the following commands:

brew install vapor/tap/vapor

Installing on Linux

This book focuses primarily on using Xcode and macOS for developing your apps. However, everything you build with Vapor will work on versions of Linux that Swift supports. The Vapor Toolbox works in exactly the same way, with the exception that you can’t use Xcode on Linux.

Installing Swift

To install Swift on Linux, go to https://swift.org/download/ and download the toolchain for your operating system. Follow the installation to install the toolchain on your machine. When complete, enter the following at a shell prompt:

swift --version

You should get the correct version of Swift returned:

Installing Vapor

In your console, run the following commands:

# 1
git clone https://github.com/vapor/toolbox.git
# 2
cd toolbox
# 3
git checkout 18.0.0
# 4
swift build -c release --disable-sandbox
# 5
mv .build/release/vapor /usr/local/bin

Here’s what this does:

  1. Clone the toolbox from GitHub.
  2. Navigate into the toolbox directory that you cloned.
  3. Check out version 18.0.0. You can find the latest release of the toolbox on the releases page on GitHub at https://github.com/vapor/toolbox/releases.
  4. Build the toolbox in release mode. --disable-sandbox allows the toolbox to execute other processes.
  5. Move the toolbox into your local path so you can call it from anywhere.

This book uses Ubuntu 20.04 throughout when referring to Linux, but the other supported versions of Linux should work in exactly the same way.

Building your first app

Setting up a Vapor project can seem complicated at first as there are a number of required files and directories. To help with this, the Toolbox can create a new project from a template. The toolbox can generate templates for a simple API, websites and authentication. You can even create your own templates.

First, create a new directory in your home directory or somewhere sensible to work on your Vapor projects. For example, enter the following commands in Terminal:

mkdir ~/vapor
cd ~/vapor

This creates a new directory in your home folder called vapor and navigates you there. Next, create your project with:

vapor new HelloVapor

The toolbox then asks if you’d like to use Fluent. For now, type n followed by Enter. You’ll learn about Fluent later. The toolbox then generates your project for you.

You should see the following:

To build and start your app, run:

# 1
cd HelloVapor
# 2
swift run

Here’s what this does:

  1. cd is the “Change Directory” command and takes you into the project directory.
  2. This builds and runs the app. It can take some time the first time since it must fetch all the dependencies.

The template has a predefined route, so open your browser and visit http://localhost:8080/hello and see the response!

Swift Package Manager

Vapor Toolbox uses Swift Package Manager, or SwiftPM, — a dependency management system similar to CocoaPods on iOS — to configure and build Vapor apps. Open your project directory and look at the structure. On macOS in Terminal, enter:

open .

Notice there’s no Xcode project in your template even though you’ve built and run the app. This is deliberate. In fact, the project file is explicitly excluded from source control using the .gitignore file. When using SwiftPM, Xcode creates a workspace in a hidden directory called .swiftpm.

A SwiftPM project is defined in the Package.swift manifest file. It declares targets, dependencies and how they link together. The project layout is also different from a traditional Xcode project. There is a Tests directory for tests. There is a Sources directory for source files. Each module defined in your manifest has its own directory inside Sources. Your sample app has an App module and a Run module, so Sources contains an App directory and a Run directory.

Inside the Run directory, there’s a single file: main.swift. This is the entry point required by all Swift apps.

Note: On iOS, this is usually synthesized with a @UIApplicationMain attribute on the AppDelegate.

The template contains everything you need to set up your app and you shouldn’t need to change main.swift or the Run module. Your code lives in App or any other modules you define.

Creating your own routes

Note: This section, as does most of the book, uses Xcode. If you’re developing on Linux, use your favorite editor, then use swift run to build and run your app.

Now that you’ve made your first app, it’s time to see how easy it is to add new routes with Vapor. If the Vapor app is still running, stop it by pressing Control-C in Terminal. Next enter:

open Package.swift

This opens the project in Xcode as a SwiftPM workspace. It will take a couple of minutes for Xcode to download the dependencies. When it’s finished, open routes.swift in Sources/App. You’ll see the route you visited above.

To create another route, add the following after the app.get("hello") closure:

app.get("hello", "vapor") { req -> String in
  return "Hello Vapor!"
}

Here’s what this does:

  • Add a new route to handle a GET request. Each parameter to app.get is a path component in the URL. This route is invoked when a user enters http://localhost:8080/hello/vapor as the URL.
  • Supply a closure to run when this route is invoked. The closure receives a Request object; you’ll learn more about these later.
  • Return a string as the result for this route.

In the Xcode toolbar, select the HelloVapor scheme and choose My Mac as the device.

Build and run. In your browser, visit http://localhost:8080/hello/vapor.

What if you want to say hello to anyone who visits your app? Adding every name in the world would be quite impractical! There must be a better way. There is, and Vapor makes it easy.

Add a new route that says hello to whomever visits. For example, if your name is Tim, you’ll visit the app using the URL http://localhost:8080/hello/Tim and it says “Hello, Tim!”.

Add the following after the code you just entered:

// 1
app.get("hello", ":name") { req -> String in
  // 2
  guard let name = req.parameters.get("name") else {
    throw Abort(.internalServerError)
  }
  // 3
  return "Hello, \(name)!"
}

Here’s the play-by-play:

  1. Use :name to designate a dynamic parameter.
  2. Extract the user’s name, which is passed in the Request object. If Vapor can’t find a parameter called name, throw an error.
  3. Use the name to return your greeting.

Build and run. In your browser, visit http://localhost:8080/hello/Tim. Try replacing Tim with some other values.

Accepting data

Most web apps must accept data. A common example is user login. To do this, a client sends a POST request with a JSON body, which the app must decode and process. To learn more about POST requests and how they work, see Chapter 3, “HTTP Basics.”

Vapor makes decoding data easy thanks to its strong integration with Swift’s Codable protocol. You give Vapor a Codable struct that matches your expected data, and Vapor does the rest. Create a POST request to see how this works.

This book uses the RESTed app, available as a free download from the Mac App Store. If you like, you may use another REST client to test your APIs.

Set up the request as follows:

  • URL: http://localhost:8080/info
  • Method: POST
  • Add a single parameter called name. Use your name as the value.
  • Select JSON-encoded as the request type. This ensures that the data is sent as JSON and that the Content-Type header is set to application/json. If you’re using a different client, you may need to set this manually.

Your request should look similar to the following:

Go back to Xcode, open routes.swift and add the following to the end of the file to create a struct called InfoData to represent this request:

struct InfoData: Content {
 let name: String
}

This struct conforms to Content which is Vapor’s wrapper around Codable. Vapor uses Content to extract the request data, whether it’s the default JSON-encoded or form URL-encoded. InfoData contains the single parameter name.

Next, add a new route after the app.get("hello", "vapor") closure:

// 1
app.post("info") { req -> String in
  let data = try req.content.decode(InfoData.self)
  return "Hello \(data.name)!"
}

Here’s what this does:

  1. Add a new route handler to handle a POST request for the URL http://localhost:8080/info. This route handler returns a String.
  2. Decode the request’s body using InfoData.
  3. Return the string by pulling the name out of the data variable.

Build and run the app. Send the request from RESTed and you’ll see the response come back:

This may seem like a lot of boilerplate to extract a single parameter from JSON. However, Codable scales up and allows you to decode complex, nested JSON objects with multiple types in a single line.

Returning JSON

Vapor also makes it easy to return JSON in your route handlers. This is a common need when your app provides an API service. For example, a Vapor app that processes requests from an iOS app needs to send JSON responses. Vapor again uses Content to encode the response as JSON.

Open routes.swift and add the following struct, called InfoResponse, to the end of the file to return the incoming request:

struct InfoResponse: Content {
  let request: InfoData
}

This struct conforms to Content and contains a property for the request.

Next, replace app.post("info") with the following:

// 1
app.post("info") { req -> InfoResponse in
  let data = try req.content.decode(InfoData.self)
  // 2
  return InfoResponse(request: data)
}

Here’s what changed:

  1. The route handler now returns the new InfoResponse type.
  2. Construct a new InfoResponse type using the decoded request data.

Build and run the app. Send the same request from RESTed. You’ll see a JSON response containing your original request data:

Troubleshooting Vapor

Throughout the course of this book, and in any future Vapor apps, you may encounter errors in your projects. There are a number of steps to take to troubleshoot any issues.

Update your dependencies

Another scenario you may encounter is hitting a bug in Vapor or another dependency you use. Make sure you are on the latest package version of any dependencies to see if the update fixes the issue. In Xcode, choose File ▸ Swift Packages ▸ Update to Latest Package Versions. If you’re running the app in Terminal, or on Linux, type:

swift package update

This SwiftPM command pulls down any updates to your dependencies and use the latest releases you support in Package.swift. Note that while packages are in the beta or release candidate stages, there may be breaking changes between updates.

Clean and rebuild

Finally, if you are still having issues, you can use the software equivalent of “turn it off and on again”. In Xcode, use Command-Option-Shift-K to clean the build folder.

You may also need to clear your derived data for the Xcode project as well and the workspace itself. The “nuclear” option involves:

  • Remove the .build directory to remove any build artifacts from the command line.
  • Remove the .swiftpm directory to delete the Xcode workspace and any misconfigurations.
  • Remove Package.resolved to ensure you get the latest dependencies next time you build.
  • Remove DerivedData to clear extra Xcode build artifacts.

Vapor Discord

The steps above usually fix most issues you might encounter that aren’t caused by your code. If all else fails, head to Vapor’s Discord server. There you’ll find thousands of developers discussing Vapor, its changes and helping people with issues. Click the Join Chat button on Vapor’s web site: https://vapor.codes.

Where to go from here?

This chapter provides an overview of how to get started with Vapor and how to create basic routes. The first two sections of this book show you how to build a complex app, including an API, a website, and authentication in both parts. As you progress through them, you’ll learn how to use core Vapor concepts, such as futures, Fluent and Leaf. By the end of section 2, you’ll have a solid foundation on which to build any server-side Swift app in Vapor.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.

Have feedback to share about the online reading experience? If you have feedback about the UI, UX, highlighting, or other features of our online readers, you can send them to the design team with the form below:

© 2020 Razeware LLC