Diving into Vapor, Part 3: Introduction to Routing and Fluent in Vapor 3

Caleb Kleveter
The Swift Web Developer
5 min readMay 5, 2018

--

In that last tutorial, we learned how to connect a database to our app and save models to it. In this tutorial, we will learn how to query that database to get, update, or delete that data.

Note: I know I promised we would discuss querying and routing in this tutorial. I tried but it just was getting too long for that 😞. We will dive a little deeper next time where we will look at more complex querying andpivot tables.

Before we start adding routes though, we are going to make a change to the User model. As it currently is built, the unique property of the model is the id property, which is a UUID. Wouldn't it make sense to have the username property be unique? It's actually pretty simple to do that.

The PostgreSQLUUIDModel sets the Database and IDtypes and idKey property of the model that conforms to it. To make the ID type a string, we need to conform to Model and implement the requirements manually:

Then remove the id property from the User model and make the username property optional:

We need to update the database structure for this change. Add the following snippet to the global configure method:

Then go to the project in the terminal and run vapor build && vapor run revert --all.

Routing 101

Routing is based around the idea of a controller. A controller is simply a set of methods that take in a request and return a response, wrapped in a class.

We will create a simple API for our User model. Create a UserController.swift file in the Controllers directory using touch Sources/App/Controllers/UserController.swiftand regenerate your Xcode project. Import both the Vapor and Fluent modules.

As said before, a controller is a class. Create a new UserController class marked final and conforming to RouteCollection. To conform to RouteCollection, you need a method with the signature boot(router: Router)throws. Leave the body of the method empty for now.

A RESTful API typically allows you to run CRUD operations on a model. We will follow the same model. We will start with the read operation because it is easier to understand the mechanics.

Add the following method to your UserController class:

This method takes in a Request object and returns an array of User models wrapped in a Future.

A Request is actually closer to a request container than an actual request. It has additional functions such as an event loop and the ability to create a connection to the database. The actual request data is in request.http.

The reason we return a Future instead of a straight User model array is because Vapor 3 is asynchronous. Fetching data from the database takes time, and we don't want to hog up threads, so instead we while we wait for the operation to complete, we allow other requests to run.

The Basics of Querying

In the body of the index method, we run query on the User model. We create the query with the User.query(on: request)method. When we use .all(), we run the query and fetch all the results of the query as an array. Because we don't need to run any operations on those users, we just return them from the method.

Create another route handler with the name show that returns a single User model in a Future. This route will be used to get a user with a given username. The path for this route will look like /users/:username. For this to work, we need to conform User to Parameter. The implementation is already done for us, so we don't have to worry about that.

The body for the show handler is also very simple. All we have to do is get the User model from the request's parameters as return it:

To create a new User and save it to the database, we need to decode the User from the request's body and call .create(on:)on it. Vapor has built in router methods that decode the body for you, so all we need to do is accept a User model as a second parameter in our route handler:

To update a user, we will take in a decoded request body and update the user’s properties with the values. We will get the user from the route parameters. We then save the user and return it.

The type used to update will look like the following. Place it in UserController.swift (or a new file if you prefer):

The update method will look like this:

Finally we will add a delete route handler where we get a user from the route parameters and call .delete(on:). According to RFC 7231, a 204 (No Content) status code should be returned when a there is no additional content to send in the response body, so we will chain a .transform(to:) call to the delete:

Registering Routes

To register a route handler with a given path, we use the methods on the router that map to HTTP methods (.get, .post, .delete, etc.). These methods take in the path, then the handler. HTTP methods that support a body can also take in a type conforming to Content that the request body will be decoded to before the handler is run.

All the handlers in the UserController will have a root path of /users. We could ad this to the front of every path, but it makes more sense to create a route group. You could think of this as another router that automatically adds a root path and/or middleware to the handlers registered to it. Create the group in the UserController.boot(router:) method:

We then register each handler according to the HTTP method it needs:

The routes that start with User.self and UserContent.selfwill decode the body of the request to that type and pass it in to the handler.

If you want to better understand the path names for each handler, this resource will be helpful.

Your UserController.swift file should now look like this:

Finally, to register the routes with application’s route, go to the global routes(_:) function, delete the current implementation, and call router.register(controller:):

Great Job! You have implemented the basic CRUD operations for the User model! The source-code for this project can be found here. If you have any questions, comments, or just want to chat, head over to our Discord server. See you there!

--

--

Caleb Kleveter
The Swift Web Developer

iOS and Vapor Fanatic. Has lots of fun open sourcing on GitHub.