Skip to Content
Course content

96: Fluent ORM for Vapor

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Alright, so we've got our Vapor routes working, but everything is currently living in memory. The second we restart the server, our data vanishes. We need a real database. I've always preferred Fluent because it lets us think in Swift objects rather than writing raw SQL strings, which is just asking for a syntax error at 3 AM.

I want to build a simple "Reading List" app. I'll start by trying to create a Book model. My first instinct is just to make a standard Swift class, but let's see if that actually works with Fluent.

The "Just a Class" Fail

class Book {
    var title: String
    var author: String
    
    init(title: String, author: String) {
        self.title = title
        self.author = author
    }
}

If I try to call book.save(on: database) now, the compiler is going to scream at me. Why? Because Book is just a plain object. Fluent has no idea how this maps to a database table. It doesn't know which property is the primary key or what the column names should be. To fix this, I need to conform to Model.

Mapping Objects to Tables

I'll adjust the model. I need to use the @ID and @Field property wrappers. These are the "magic" parts of Fluent that handle the translation between Swift and SQL.

import Fluent
import Vapor

final class Book: Model {
    static let schema = "books" // This is the actual table name in SQL

    @ID(key: .id)
    var id: UUID?

    @Field(key: "title")
    var title: String

    @Field(key: "author")
    var author: String

    init() { } // Fluent needs an empty init for decoding from the DB

    init(id: UUID? = nil, title: String, author: String) {
        self.id = id
        self.title = title
        self.author = author
    }
}

Now the compiler is happy, but if I run the app and try to save a book, I'll get a runtime error saying the table books doesn't exist. I forgot that databases are rigid. I can't just define a class in Swift and expect the database to magically create the table. I need a Migration.

Teaching the Database the Schema

I'll create a migration file. Think of this as a version-control script for your database schema. I'm telling Fluent: "Hey, create a table called 'books' with these specific columns."

struct CreateBook: Migration {
    func prepare(on database: Database) -> EventLoopFuture<Void> {
        database.schema("books")
            .id()
            .field("title", .string, .required)
            .field("author", .string, .required)
            .create()
    }

    func revert(on database: Database) -> EventLoopFuture<Void> {
        database.schema("books").delete()
    }
}

I've got the migration, but it's just sitting there. I need to actually tell Vapor to run it. I'll head over to configure.swift and add it to the app's migrations list. I'll also make sure I've configured a database driver—I'll use SQLite for now because it's just a file on disk and doesn't require a separate server setup.

app.databases.use(.sqlite(.file("db.sqlite")), as: .sqlite)
app.migrations.add(CreateBook())

Testing the Persistence

Now for the moment of truth. Let's create a route that actually saves a book and then fetches it back. I'll use a simple POST request to save and a GET to verify.

app.post("books") { req -> EventLoopFuture<Book> in
    let book = try req.content.decode(Book.self)
    return book.save(on: req.db).map { book }
}

app.get("books") { req -> EventLoopFuture<[Book]> in
    return Book.query(on: req.db).all()
}

I'll fire off a request with {"title": "The Swift Programming Language", "author": "Apple"}. It works! The data is now sitting in db.sqlite. If I stop the server and start it again, the books are still there. That's the power of the ORM—I'm interacting with Swift types, and Fluent is handling the heavy lifting of translating those into SQL queries behind the scenes.




📋 Practical Task

Exercise: Implementing a Reading List Category System

Currently, our Book model only has a title and an author. To make this a real application, we need to categorize our books (e.g., "Technical", "Fiction", "Biography").

Your Task:

  • Modify the Book model to include a new @Field called category (a String).
  • Create a new Migration called AddCategoryToBook that updates the existing books table to add the category column. Note: Do not delete the first migration; create a new one to evolve the schema.
  • Register the new migration in configure.swift.
  • Update your POST route to accept the category in the request body and save it to the database.
  • Verify the change by saving a book with a category and retrieving it via the GET route.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.