Home iOS & Swift Books Server-Side Swift with Vapor

8
Controllers Written by Tim Condon

In previous chapters, you’ve written all the route handlers in routes.swift. This isn’t sustainable for large projects as the file quickly becomes too big and cluttered. This chapter introduces the concept of controllers to help manage your routes and models, using both basic controllers and RESTful controllers.

Note: This chapter requires that you have set up and configured PostgreSQL. Follow the steps in Chapter 6, “Configuring a Database”, to set up PostgreSQL in Docker and configure the Vapor application.

Controllers

Controllers in Vapor serve a similar purpose to controllers in iOS. They handle interactions from a client, such as requests, process them and return the response. Controllers provide a way to better organize your code. It’s good practice to have all interactions with a model in a dedicated controller. For example in the TIL application, an acronym controller can handle all CRUD operations on an acronym.

Controllers are also used to organize your application. For instance, you may use one controller to manage an older version of your API and another to manage the current version. This allows a clear separation of responsibilities in your code and keeps code maintainable.

Getting started with controllers

In Xcode, create a new Swift file to hold the acronyms controller. Create the file in Sources/App/Controllers and call it AcronymsController.swift.

Route collections

Inside a controller, you define different route handlers. To access these routes, you must register these handlers with the router. A simple way to do this is to call the functions inside your controller from routes.swift. For example:

app.get(
  "api",
  "acronyms",
  use: acronymsController.getAllHandler)
import Vapor
import Fluent

struct AcronymsController: RouteCollection {
  func boot(routes: RoutesBuilder) throws {
  }
}
func getAllHandler(_ req: Request) throws 
    -> EventLoopFuture<[Acronym]> {
  Acronym.query(on: req.db).all()
}
routes.get("api", "acronyms", use: getAllHandler)
app.get("api", "acronyms") { 
  req -> EventLoopFuture<[Acronym]> in
  Acronym.query(on: req.db).all()
}
// 1
let acronymsController = AcronymsController()
// 2
try app.register(collection: acronymsController)

Route groups

All of the REST routes created for acronyms in the previous chapters use the same initial path, e.g.:

app.post("api", "acronyms") { 
  req -> EventLoopFuture<Acronym> in
  let acronym = try req.content.decode(Acronym.self)
  return acronym.save(on: req.db).map { acronym }
}
let acronymsRoutes = routes.grouped("api", "acronyms")
routes.get("api", "acronyms", use: getAllHandler)
acronymsRoutes.get(use: getAllHandler)
func createHandler(_ req: Request) throws 
    -> EventLoopFuture<Acronym> {
  let acronym = try req.content.decode(Acronym.self)
  return acronym.save(on: req.db).map { acronym }
}

func getHandler(_ req: Request) throws 
    -> EventLoopFuture<Acronym> {
  Acronym.find(req.parameters.get("acronymID"), on: req.db)
    .unwrap(or: Abort(.notFound))
}

func updateHandler(_ req: Request) throws 
    -> EventLoopFuture<Acronym> {
  let updatedAcronym = try req.content.decode(Acronym.self)
  return Acronym.find(
    req.parameters.get("acronymID"),
    on: req.db)
    .unwrap(or: Abort(.notFound)).flatMap { acronym in
      acronym.short = updatedAcronym.short
      acronym.long = updatedAcronym.long
      return acronym.save(on: req.db).map {
        acronym
      }
    }
}

func deleteHandler(_ req: Request) throws 
    -> EventLoopFuture<HTTPStatus> {
  Acronym.find(req.parameters.get("acronymID"), on: req.db)
    .unwrap(or: Abort(.notFound))
    .flatMap { acronym in
      acronym.delete(on: req.db)
        .transform(to: .noContent)
    }
}

func searchHandler(_ req: Request) throws 
    -> EventLoopFuture<[Acronym]> {
  guard let searchTerm = req
    .query[String.self, at: "term"] else {
      throw Abort(.badRequest)
  }
  return Acronym.query(on: req.db).group(.or) { or in
    or.filter(\.$short == searchTerm)
    or.filter(\.$long == searchTerm)
  }.all()
}

func getFirstHandler(_ req: Request) throws 
    -> EventLoopFuture<Acronym> {
  return Acronym.query(on: req.db)
    .first()
    .unwrap(or: Abort(.notFound))
}

func sortedHandler(_ req: Request) throws 
    -> EventLoopFuture<[Acronym]> {
  return Acronym.query(on: req.db)
    .sort(\.$short, .ascending).all()
}
// 1
acronymsRoutes.post(use: createHandler)
// 2
acronymsRoutes.get(":acronymID", use: getHandler)
// 3
acronymsRoutes.put(":acronymID", use: updateHandler)
// 4
acronymsRoutes.delete(":acronymID", use: deleteHandler)
// 5
acronymsRoutes.get("search", use: searchHandler)
// 6
acronymsRoutes.get("first", use: getFirstHandler)
// 7
acronymsRoutes.get("sorted", use: sortedHandler)

Where to go from here?

This chapter introduced controllers as a way of better organizing code. They help split out route handlers into separate areas on responsibility. This allows applications to grow in a maintainable way. The next chapters look at how to bring together different models with relationships in Fluent.

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

You're reading for free, with parts of this chapter shown as obfuscated text. Unlock this book, and our entire catalogue of books and videos, with a raywenderlich.com Professional subscription.

Unlock Now

To highlight or take notes, you’ll need to own this book in a subscription or purchased by itself.