Chapters

Hide chapters

Server-Side Swift with Vapor

Third Edition - Early Acess 1 · iOS 13 · Swift 5.2 - Vapor 4 Framework · Xcode 11.4

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section I: Creating a Simple Web API

Section 1: 13 chapters
Show chapters Hide chapters

8. Controllers
Written by Tim Condon

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

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.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now