Fluent And Controllers - Part 1

Written by Tim on Monday, September 11, 2017. Last edited on Monday, September 11, 2017.

This is the second tutorial of a series of tutorials on Vapor. For more, click here

Fluent

In the last tutorial we learnt how to get started with Vapor and create a new project from a template. However, now we want to actually be able to do something with it! As I mentioned before, this series of tutorials will be based on creating a Reminders app, so today we are going to learn how to save and retrieve reminders from a database.

Before we get started, we need to remove all of the stuff from the template that we aren't going to use. So delete the tests directory (don't worry we'll come back to tests in a future tutorial!) and the files from the Controllers and Models directories in the Sources/App directory:

rm -rf Tests/ Sources/App/Models/Post.swift Sources/App/Controllers/PostController.swift

Fluent

Fluent is Vapor's ORM, and it is basically an abstraction layer between your code and whichever database you choose, and Fluent supports MySQL, PostgreSQL and MongoDB. An ORM has a number of advantages: to start with it removes all of the heavy lifting of interacting with a database, so you don't have to worry about having to set up your connection for each query. It handles all of the code of converting your model from SQL to a concrete type (which is especially helpful with such a strictly-typed language as Swift). It also provides some safety by using prepared statements by default, so you shouldn't be susceptible to SQL injection attacks. Finally, with a generic ORM such as Fluent, it is trivial to switch database types in most cases. Want to change from MySQL to Postgres? It's easy, all you have to do is change the provider you bring in and your database configuration - there are no changes required in your code. This is especially useful when writing libraries, since anyone taking your library can use whatever database they want. It also provides a massive advantage for testing - your production environment can use a MySQL database, but you can just use an in-memory SQLite DB for all of your unit tests.

Note: by default, the templates use an in-memory database so each time you run your application you will start from scratch. This is useful for getting started but we will talk about how to persist your database in a later tutorial.

Creating A Model

A Fluent Model is a Swift representation of the data that will be stored in your database and is what pretty much all of Fluent will use. So as we are writing a reminders app, let's create the first model for our app - the reminder! This is going to be pretty simple to start with, we just want a title and description. So first we will create a new file in the Models directory to contain our model code. Once we have done that, we need to regenerate the Xcode project so it can pick up all the file changes (with Swift Package Manager you will find yourself doing this a lot!):

touch Sources/App/Models/Reminder.swift
vapor xcode -y

Once Xcode has opened, we can fill in the code for our model. This will be a class that conforms to Model, which means we need to implement the init(row:) and makeRow() functions; these are how Fluent converts your models from the database to Swift and Swift to database respectively. So our class will look something like:

import FluentProvider

final class Reminder: Model {
    let storage = Storage()

    let title: String
    let description: String

    init(title: String, description: String) {
        self.title = title
        self.description = description
    }

    init(row: Row) throws {
        title = try row.get("title")
        description = try row.get("description")
    }

    func makeRow() throws -> Row {
        var row = Row()
        try row.set("title", title)
        try row.set("description", description)
        return row
    }
}

This is all we need to be able to store and retrieve our simple model in the database table. You will notice the Storage property - this is used by Fluent to store various bits of model information in, such as the ID of the model and whether it has been created or not etc. The get and set functions of Row will infer the types of the data to get out by the type of the property, which is pretty neat!

Preparations

Fluent also manages all of your database tables for you and when your app first runs it will check a list of preparations you give it and if it hasn't run then, will execute the preparations. This is how you create your tables! So we need to add an extension so our model so it will create the database:

extension Reminder: Preparation {
    static func prepare(_ database: Database) throws {
        try database.create(self) { builder in
            builder.id()
            builder.string("title")
            builder.string("description")
        }
    }

    static func revert(_ database: Database) throws {
        try database.delete(self)
    }
}

This is how you create your tables and builder.string("title") tells it to create a column on the table of type string, which depending on the underlying database will use a VARCHAR(256). You can use builder.date, builder.bool, builder.int etc for different column types. Once you have created your preparation, you need to add it to the list of preparations that you give to your configuration so the table is created when the app is first run.

Open up Config+Setup.swift and in setupPreparations() remove the Post preparation that was leftover from the template and add in our new model, so the function will look something like:

private func setupPreparations() throws {
    preparations.append(Reminder.self)
}

Controllers

Controllers are a way to group functionality of your Vapor app. So for something like Twitter you may have a Tweet Controller, a User Controller, a Moments Controller etc. They can be really useful for splitting up massive route file into related routes. For us, we will have a Reminder Controller that will deal with the reminders in our app. So create a new file, RemindersController.swift in the Sources/App/Controllers directory and regenerate the Xcode project:

touch Sources/App/Controllers/RemindersController.swift
vapor xcode -y

So our controller is going to be responsible for three things:

  • Creating a reminder
  • Viewing all of the reminders
  • Viewing a single reminder

Setting Up Our Controller

Before we deal with the 3 cases we need to set our controller up so that we can route the three different requests to the right function and make sure the routes get registered on our Droplet. So in our file, we will create the skeleton code for our controller:

import Vapor
import FluentProvider

struct RemindersController {
    func addRoutes(to drop: Droplet) {
        let reminderGroup = drop.grouped("api", "reminders")
    }
}

Here we are creating a route group, so that all of the routes we add on our controller with be under /api/reminders/. We then need to add this controller to our Droplet so in Routes.swift (where all of the routes are defined for this template), delete everything from setupRoutes as this is leftover from the template. Once this has done, you can create an instance of our controller and setup the routes:

extension Droplet {
    func setupRoutes() throws {
        let remindersController = RemindersController()
        remindersController.addRoutes(to: self)
    }
}

Creating A Reminder

For this case we want to be able to send a POST request with some JSON to /api/reminders/create to be able to create a reminder. To start this we need to be able to represent our reminder as JSON. This is easy to do in Vapor with a protocol conformance extension to JSONCovertible. This is formed of two protocols, JSONInitializable and JSONRepresentable, which allows us to create a model from JSON and to create JSON from our model. So add the extension to the bottom of our Reminder.swift code:

extension Reminder: JSONConvertible {
    convenience init(json: JSON) throws {
        try self.init(title: json.get("title"), description: json.get("description"))
    }

    func makeJSON() throws -> JSON {
        var json = JSON()
        try json.set("id", id)
        try json.set("title", title)
        try json.set("description", description)
        return json
    }
}

extension Reminder: ResponseRepresentable {}

Also note, that because we can create a JSON representation of our model, we can use an empty protocol conformance extension to conform to ResponseRepresentable which just allows us to return our model for a response.

Now that that is set up, we can create the route inside our RemindersController:

func createReminder(_ req: Request) throws -> ResponseRepresentable {
    guard let json = req.json else {
        throw Abort.badRequest
    }
    let reminder = try Reminder(json: json)
    try reminder.save()
    return reminder
}

Here we make sure that we have some JSON with the sent request, and then try and convert that to the model. Once we have a model object we can save it, which is Fluent simply means calling .save() on it! We then return our newly created reminder as a response. Now we have our route handler, we can register the route in our addRoutes() function:

func addRoutes(drop: Droplet) {
    let reminderGroup = drop.grouped("api", "reminders")
    reminderGroup.post("create", handler: createReminder)
}

Notice here that we are adding a POST route at /api/reminders/create and then linking it to our newly created route handler function above. We should now be able to create a reminder! So build and run the app (CMD+R in Xcode) and open up your favourite REST client. I use Rested which despite not having been updated for many years, still works really well as a simple client.

Create a POST request with a JSON body with something like:

{
  "description": "Milk and eggs",
  "title": "Shopping List"
}

Send it to http://localhost:8080/api/reminders/create and hopefully you should get a 200 OK response back with your newly created reminder!

Create a reminder

Viewing All Reminders

Now that we can create reminders, it would be great if we could see all of our reminders! With Fluent and all of the setup we have done, this is remarkably easy. Our route handler will simply look like:

func allReminders(_ req: Request) throws -> ResponseRepresentable {
    let reminders = try Reminder.all()
    return try reminders.makeJSON()
}

The .al() function from Fluent simply gets all of our reminders from the database and then all we have to do is return the JSON for all of them with makeJSON() on our array of models. We can then simply register our route, which will be a GET request to /api/reminders:

func addRoutes(to drop: Droplet) {
    let reminderGroup = drop.grouped("api", "reminders")
    reminderGroup.post("create", handler: createReminder)
    reminderGroup.get(handler: allReminders)
}

By not specifying a path for the route, it will simply be registered to the root path of the route group. So build and run your app again and create a few reminders (remember, you will lose all previously created reminders when you restart your application for now), and set a GET request to http://localhost:8080/api/reminders and you should see a JSON representation of all of your reminders:

Create a reminder

Viewing A Single Reminder

The last thing we want to be able to do for this tutorial is to be able to get a single reminder from our API. Again with Vapor this is really easy as it does all of the heavy lifting for us. Normally we would expect to pass an ID in our route which we would then extract in our route handler; we would then take that ID and do a look up for the model of that ID, making sure we select the correct table for that route and then convert that database data into a model. With Fluent and Vapor, this is done in a single line, so our route handler is really simple:

func getReminder(_ req: Request) throws -> ResponseRepresentable {
    let reminder = try req.parameters.next(Reminder.self)
    return reminder
}

This will automatically pull out our model for us or throw an error if it can't find it - and it is type safe! To use this, we just need to specify the parameter when registering our route:

func addRoutes(to drop: Droplet) {
    let reminderGroup = drop.grouped("api", "reminders")
    reminderGroup.get(handler: allReminders)
    reminderGroup.post("create", handler: createReminder)
    reminderGroup.get(Reminder.parameter, handler: getReminder)
}

For the last route, our path will look like /api/reminders/<ID OF REMINDER> and Vapor will do the rest! Build and run the app again and create a reminder once more. If you look at the first reminder we created above, you will notice it returns an ID in the JSON response. So once you have created a reminder, take the ID, which is 1 in this case and send a GET request to http://localhost:8080/api/reminders/1 and you should get the JSON for that reminder back:

Get a reminder

Conclusion

Hopefully you found that relatively simple to start saving data in the database and seeing how your app can start to evolve with controllers. We can now create and get models from the database via an API. Next time we will take a deeper look into Fluent and see how we can model relationships between different models in the database.

All of the code for these tutorials can be found on Github, with this tutorial under the tutorial-2/fluent-and-controllers-1 tags.