How to receive a POST request with server side Swift using Vapor

May 21, 2018
Written by
Sam Agnew
Twilion

Screen Shot 2018-05-21 at 9.41.09 PM

After displaying text on a web page, receiving a POST request is the “Hello, World” v2 of building a web app. Let’s walk through how to do that using the popular server side Swift web framework, Vapor.

Swift Package Manager

For this project we’re going to use Swift Package Manager to set everything up and install dependencies. Make sure you have Swift 4.0 or greater installed before moving on. You can check your Swift version by running the following in your terminal:

swift --version

Vapor has a convenient command line tool which can be used to generate templates for projects. We won’t be using that functionality in this post, as our needs are more simple, but installing this will also install other necessary dependencies.

You can install it using Homebrew with the following command:

brew tap vapor/homebrew-tap
brew install vapor

Now initiate a new Swift project with the following terminal command in the directory where you want your code to live:

swift package init --type executable

This will generate a directory structure for your Swift project. By default it will name your project after the directory you ran the previous command in. My project was named Hello, so you may need to replace that in any of the commands or code further in this post that refer to your Swift project.

Next let’s get our dependencies set up. Open the file Package.swift and add the Vapor package to the code Swift Package Manager generated. My Package.swift looks like this:

// swift-tools-version:4.0
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
    name: "Hello",
    dependencies: [
        // Dependencies declare other packages that this package depends on.
        // .package(url: /* package url */, from: "1.0.0"),
        .package(url: "https://github.com/vapor/vapor.git", from: "3.0.0-rc")
    ],
    targets: [
        // Targets are the basic building blocks of a package. A target can define a module or a test suite.
        // Targets can depend on other targets in this package, and on products in packages which this package depends on.
        .target(
            name: "Hello",
            dependencies: ["Vapor"]),
    ]
)

Getting started with Vapor

We’re now ready to move on to writing some Swift code. From the base directory of your project, open the file Sources/Hello/main.swift and add the following code to it:

import Vapor

let app = try Application()
let router = try app.make(Router.self)

router.get("hello") { req in
    return "Hello, world."
}

try app.run()

This is the bare bones “Hello World” Vapor application. It has one route that takes a GET request and responds with a simple string. With your Package.swift configured and your Vapor code written, run the following commands to build and run the project:

swift build
./.build/debug/Hello

This will install Vapor and all of its dependencies and will run your web application. Once it is running, visit http://localhost:8080/hello to see a basic “Hello World” webpage.

Parsing a POST request body

The next step is to start handling POST requests. There are many ways to get data out of the body of a POST request, but the preferred way with Vapor is to create a Codable struct to map the data from the request body to.

As a quick example, let’s treat the incoming request similarly to one you would receive using the Twilio SMS API for when an incoming text message is sent to your Twilio number. Let’s create a content struct to represent the data of a text message. Add the following code to main.swift after the import Vapor statement:

struct MessageRequest: Content {
    var to: String
    var from: String
    var body: String
}

Now that we have our struct, all that’s left to do is create a new route to receive POST requests. In this route, we’ll decode the request body using our new MessageRequest struct. Add the following code to main.swift after the “hello” route:

router.post("sms") { req -> Future<HTTPStatus> in
    return try req.content.decode(MessageRequest.self).map(to: HTTPStatus.self) { messageRequest in
        print("To: (messageRequest.to)")
        print("From: (messageRequest.from)")
        print("Body: (messageRequest.body)")
        return .ok
    }
}

Let’s use the curl command to see if this route works. Build and run your code again, and run the following command in another terminal window:

curl -d "to= 19999999999&from= 18888888888&body=Hello" -X POST http://localhost:8080/sms

In the terminal window running your Vapor app, you should see something like this printed to the console when the request is received:

Opening your web app to the internet

Now you can receive and parse POST requests, but this isn’t much use if your web application is just running on localhost! You could host it somewhere, but if you’re just testing stuff for development we can use a tool called ngrok. Ngrok opens a tunnel from a port on your machine to a publicly accessible URL. This is a life saver if you need to test some code for a webhook.

You can download ngrok here. Once it’s installed you can create an http tunnel on port 8080 with the following command:

ngrok http 8080

If you’ve just installed ngrok and that previous command didn’t work, you might have to run it like ./ngrok http 8080 from the directory that the ngrok executable is in. Once ngrok is running your terminal should look something like this:

Try visiting your ngrok URL in the browser and appending “/hello” to it to see the “hello” route on your web app. You can also run the previous curl command while replacing the “localhost:8080” part with your ngrok URL and everything should work the same. Your server side Swift app is now accessible on the internet…until you stop ngrok or close your laptop!

Wrapping up

Now you’re equipped to handle incoming POST requests in your server side Swift applications. This can be useful whether you’re developing backend APIs for your iOS apps, dealing with webhooks for services like Twilio or submitting form data from a client side web application.

For more in depth projects with Vapor, you could also use the Vapor command line tool to generate project templates and handle the Swift Package Manager stuff for you.

If you have any other cool ideas, feel free to reach out and let me know or ask any questions: