Home iOS & Swift Books Server-Side Swift with Vapor

7
CRUD Database Operations Written by Tim Condon

Chapter 5, “Fluent and Persisting Models”, explained the concept of models and how to store them in a database using Fluent. This chapter concentrates on how to interact with models in the database. You’ll learn about CRUD operations and how they relate to REST APIs. You’ll also see how to leverage Fluent to perform complex queries on your models.

Note: This chapter requires you to use PostgreSQL. Follow the steps in Chapter 5, “Fluent and Persisting Models”, to set up PostgreSQL in Docker and configure your Vapor application.

CRUD and REST

CRUD operations — Create, Retrieve, Update, Delete — form the four basic functions of persistent storage. With these, you can perform most actions required for your application. You actually implemented the first function, create, in Chapter 5.

RESTful APIs provide a way for clients to call the CRUD functions in your application. Typically you have a resource URL for your models. For the TIL application, this is the acronym resource: http://localhost:8080/api/acronyms. You then define routes on this resource, paired with appropriate HTTP request methods, to perform the CRUD operations. For example:

Create

In Chapter 5, “Fluent and Persisting Models”, you implemented the create route for an Acronym. You can either continue with your project or open the TILApp in the starter folder for this chapter. To recap, you created a new route handler in routes.swift:

// 1
app.post("api", "acronyms") { 
  req -> EventLoopFuture<Acronym> in
  // 2
  let acronym = try req.content.decode(Acronym.self)
  // 3
  return acronym.save(on: req.db).map { acronym }
}

Retrieve

For TILApp, retrieve consists of two separate operations: retrieve all the acronyms and retrieve a single, specific acronym. Fluent makes both of these tasks easy.

Retrieve all acronyms

To retrieve all acronyms, create a route handler for GET requests to /api/acronyms/. Open routes.swift and add the following at the end of routes(_:):

// 1
app.get("api", "acronyms") { 
  req -> EventLoopFuture<[Acronym]> in
  // 2
  Acronym.query(on: req.db).all()
}

Retrieve a single acronym

Vapor’s parameters integrate with Fluent’s querying functions to make it easy to get acronyms by IDs. To get a single acronym, you need a new route handler. Open routes.swift and add the following at the end of routes(_:):

// 1
app.get("api", "acronyms", ":acronymID") { 
  req -> EventLoopFuture<Acronym> in
  // 2
  Acronym.find(req.parameters.get("acronymID"), on: req.db)
    // 3
    .unwrap(or: Abort(.notFound))
}

Update

In RESTful APIs, updates to single resources use a PUT request with the request data containing the new information.

// 1
app.put("api", "acronyms", ":acronymID") { 
  req -> EventLoopFuture<Acronym> in
  // 2
  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
      }
  }
}

Delete

To delete a model in a RESTful API, you send a DELETE request to the resource. Add the following to the end of routes(_:) to create a new route handler:

// 1
app.delete("api", "acronyms", ":acronymID") { 
  req -> EventLoopFuture<HTTPStatus> in
  // 2
  Acronym.find(req.parameters.get("acronymID"), on: req.db)
    .unwrap(or: Abort(.notFound))
    // 3
    .flatMap { acronym in
      // 4
      acronym.delete(on: req.db)
        // 5
        .transform(to: .noContent)
  }
}

Fluent queries

You’ve seen how easy Fluent makes basic CRUD operations. It can perform more powerful queries just as easily.

Filter

Search functionality is a common feature in applications. If you want to search all the acronyms in the database, Fluent makes this easy. Ensure the following line of code is at the top of routes.swift:

import Fluent
// 1
app.get("api", "acronyms", "search") { 
  req -> EventLoopFuture<[Acronym]> in
  // 2
  guard let searchTerm = 
    req.query[String.self, at: "term"] else {
    throw Abort(.badRequest)
  }
  // 3
  return Acronym.query(on: req.db)
    .filter(\.$short == searchTerm)
    .all()
}

return Acronym.query(on: req.db)
  .filter(\.$short == searchTerm)
  .all()
// 1
return Acronym.query(on: req.db).group(.or) { or in
  // 2
  or.filter(\.$short == searchTerm)
  // 3
  or.filter(\.$long == searchTerm)
// 4
}.all()

First result

Sometimes an application needs only the first result of a query. Creating a specific handler for this ensures the database only returns one result rather than loading all results into memory. Create a new route handler to return the first acronym at the end of routes(_:):

// 1
app.get("api", "acronyms", "first") { 
  req -> EventLoopFuture<Acronym> in
  // 2
  Acronym.query(on: req.db)
    .first()
    .unwrap(or: Abort(.notFound))
}

Sorting results

Apps commonly need to sort the results of queries before returning them. For this reason, Fluent provides a sort function.

// 1
app.get("api", "acronyms", "sorted") { 
  req -> EventLoopFuture<[Acronym]> in
  // 2
  Acronym.query(on: req.db)
    .sort(\.$short, .ascending)
    .all()
}

Where to go from here?

You now know how to use Fluent to perform the different CRUD operations and advanced queries. At this stage, routes.swift is getting cluttered with all the code from this chapter. The next chapter looks at how to better organize your code using controllers.

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.