Episode #372

Vapor Controllers

Series: Server-side Swift with Vapor

15 minutes
Published on January 17, 2019

This video is only available to subscribers. Get access to this video and 573 others.

So far in this series we have been adding all of our routes to the routes.swift file. You can see that this would get unwieldy over time. In this episode we will use RouteCollections to build controllers so that we can organize the routes around the Projects resource. We’ll conform our Project type to the Parameter protocol to make loading models from a route parameter extremely simple, and we will leverage the Content protocol to have the results serialized to JSON automatically.

Episode Links

  • Paw - a useful API bench tool for macOS.

Adding the Projects Controller

final class ProjectsController : RouteCollection {
    func boot(router: Router) throws {
    }
}

We can then use router just like we were before to register routes that belong together. We'll use this controller to handle all routes related to retrieving and managing Projects.

Let's create the first route to list all projects:

private func index(_ req: Request) throws -> Future<[Project]> {

}

Before we can return the projects array like this, we have to conform it to the Content protocol.

extension Project : Content { }
private func index(_ req: Request) throws -> Future<[Project]> {
    return Project.query(on: req)
                     .order(\.createAt, .descending)
                     .all()
}

Since .all() returns an EventLoopFuture<[Project]> already, we're basically done.

Let's hook this up to the routing system.

First, in routes.swift we have to register this route collection. We'll do this by nesting them all under a group called "projects". This means they'll all share a common prefix in the URL, so essentially our index route will be the root of this group.

try router.grouped("projects").register(collection: ProjectsController())

Then in the controller class:

final class ProjectsController : RouteCollection {
    func boot(router: Router) throws {
        router.get(use: index)
    }
}

The Show Route

Showing a single project would require the UUID parameter in the URL. Since these map directly to the primary key of a fluent model, we can lean on the framework here to do all of the heavy lifting.

We'll start by defining the route function that will expect a primary key as the first (and only route component):

private func show(_ req: Request) throws -> Future<Project> {
    return try req.parameters.next(Project.self)
}

Then we can wire it up:

router.get(Project.parameter, use: show)

The Create Route

For this demo we'll just hard code some values so we can skip taking input from the request for now. We'll tackle this in the next episode.

private func create(_ req: Request) throws -> Future<Project> {
    let project = Project(title: "New Project", description: "New Description")
    return project.save(on: req)
}

The Update and Delete Routes

For updating, we can do something similar, however we first need to load the project from the database, so we have to leverage flatMap to stitch together the two futures.

private func update(_ req: Request) throws -> Future<Project> {
    return try req.parameters.next(Project.self)
        .flatMap { project in
            project.title = project.title + " UPDATED"
            return project.update(on: req)
    }
}

Implementing delete is similar:

private func delete(_ req: Request) throws -> Future<HTTPStatus> {
    return try req.parameters.next(Project.self)
        .flatMap { project in
            return project.delete(on: req).transform(to: .noContent)
    }
}

And in the boot function we can wire up these new routes:

    // ...
    router.post(use: create)
    router.put(Project.parameter, use: update)
    router.delete(Project.parameter, use: delete)

This episode uses Swift 4.2, Vapor 3.0.8, Xcode 10.1.