Matua Doc

Matua Doc

Introduction to GRDB.swift

Learning intentions

In this lesson, you will learn how to bridge the gap between Swift objects and database tables using GRDB.swift.

  • Explain what an ORM-like layer is and why GRDB.swift is used in Swift development.
  • Manage external dependencies by editing Package.swift.
  • Define database schemas using migrations.
  • Map Swift models to database rows using GRDB protocols.

What is GRDB.swift?

GRDB.swift is a SQLite toolkit for Swift. It gives you two ways to work with data:

  • SQL-first: write SQL directly when you want full control.
  • Record-based: map rows to Swift types for safer, cleaner code.

You can still use SQL when you need it, but GRDB gives you strong Swift typing and helpful conventions for day-to-day app code.

Setting up the dependency

To use GRDB.swift, you must tell Swift Package Manager (SPM) to download it. This is done in your Package.swift file.

1. Add the package

Add the GRDB.swift repository to your dependencies array:

let package = Package(
    name: "SwiftPlayground",
    dependencies: [
        .package(url: "https://github.com/groue/GRDB.swift", exact: "7.10.0")
    ],

2. Add to target

Add "GRDB" to the dependencies array of your specific target:

    targets: [
        .executableTarget(
            name: "SwiftPlayground",
            dependencies: [
                .product(name: "GRDB", package: "grdb.swift")
            ]
        ),
    ]
)

Once you save this file, your IDE (like Xcode or VS Code) will automatically download the library.

Complete example of Package.swift

Connecting to a .db file

GRDB works with SQLite database files that end in .db. You connect by creating a DatabaseQueue (single-threaded) or a DatabasePool (multi-threaded). For learning, DatabaseQueue is a good default.

Example: open a local database file

import GRDB

@main
struct SwiftPlayground {
    static func main() {
        let dbPath = "./library.db"
        guard let dbQueue = try? DatabaseQueue(path: dbPath) else {
            fatalError("Could not open database.")
        }
    }
}

If the file does not exist, SQLite will create it. If you want to place the file in a specific folder (like the user’s Documents directory), pass that path into DatabaseQueue(path:).

Handling throwing functions

Most GRDB APIs are marked throws because database work can fail (missing files, bad SQL, locked database, and so on). In Swift, you handle this with do/catch, try?, or by propagating the error.

Option 1: do/catch with a clear error message

do {
    let dbQueue = try DatabaseQueue(path: "./library.db")
    try dbQueue.write { db in
        try db.create(table: "books") { t in
            t.autoIncrementedPrimaryKey("id")
            t.column("title", .text).notNull()
        }
    }
} catch {
    print("Database error: \(error)")
}

Option 2: use guard let else

guard let dbQueue = try? DatabaseQueue(path: "./libary.db") else {
    fatalError("Could not open database.")
}

Using guard let ensures the queue exists, and it can then be used in later in the code until it goes out of scope. Using try? (with a question mark) makes creating the DatabaseQueue compatible with guard let since it expects an optional value.

Option 3: propagate the error

func loadDatabase() throws -> DatabaseQueue {
    try DatabaseQueue(path: "./library.db")
}

Propagating keeps the function small and lets the caller decide how to handle the failure. This is useful in larger apps where a higher-level layer (like an app coordinator or CLI entry point) does the error reporting.

Modeling data with GRDB

GRDB uses lightweight protocols to map rows to Swift types. A typical model uses:

  • FetchableRecord to load rows from the database.
  • PersistableRecord to insert and update rows.
  • Codable to map columns to Swift properties.

Requirements of a GRDB model:

  1. A table name (via databaseTableName).
  2. An ID (often an Int64? for SQLite row ids).
  3. Properties that match your columns.
  4. CodingKeys if your column names differ from Swift property names.
import GRDB

struct Movie: Identifiable, Codable, FetchableRecord, PersistableRecord {
    var id: Int
    var title: String
    var releaseYear: Int

    enum CodingKeys: String, CodingKey {
        case id = "MovieID"
        case title = "Title"
        case releaseYear = "ReleaseYear"
    }
}

GRDB does not rely on property wrappers. Instead, it uses Swift protocols and CodingKeys to map between properties and database columns.

Task A: Dependency Management

  1. Open your Package.swift file.
  2. Add the GRDB.swift package to your dependencies.
  3. Add the GRDB product to your main target.
  4. Verify that the packages resolve (download) successfully.

Task B: Creating a Model

You have been given an existing database with a Purchaser table.

  1. Create a struct called Purchaser that conforms to Codable, FetchableRecord, and PersistableRecord.
  2. Set the table name to "Purchaser".
  3. Add a primary key property purchaserID of type Int64.
  4. Add properties for name (String), count (Int), and reservedTable (String).
  5. Use CodingKeys so the database columns (PurchaserID, Name, Count, ReservedTable) map to your Swift properties.

Task C: Expanding the Schema

Create models for the remaining tables in the schema.

  1. Item with itemID (Int64), name (String), price (Double).
  2. Order with orderID (Int64), purchaserID (Int64), amount (Double).
  3. OrderLine with orderID (Int64), itemID (Int64), quantity (Int).
  4. Use CodingKeys so the database columns (ItemID, OrderID, PurchaserID) map to your Swift properties.

Task D: Using the Model

In your main.swift or test file:

  1. Create a DatabaseQueue using an in-memory database.
  2. Create tables that match the given schema with a migration.
  3. Insert one Purchaser, one Item, one Order, and one OrderLine.
  4. Fetch the order and print the purchaser name and total amount.

Note: Even before you connect to a real file-based database, an in-memory DatabaseQueue lets you practice migrations and model code safely.

Summary

GRDB.swift gives you a clean way to work with SQLite using either SQL or Swift models.

  • SQLite toolkit with SQL-first and record-based options.
  • Package.swift manages the library download.
  • FetchableRecord/PersistableRecord map rows to Swift types.
  • CodingKeys control how Swift properties map to column names.

Review

Use these prompts to check your understanding.

  • Why might you still write SQL even when using GRDB.swift?
  • What does databaseTableName do?
  • When would you use CodingKeys in a GRDB model?
  • What is the benefit of an in-memory DatabaseQueue during testing?

Extension challenge for Super Players!

  1. Research DatabaseMigrator in GRDB.swift.
  2. Add created_at and updated_at columns to your books table.
  3. Explain in a comment how these timestamps help a developer track data changes.