Skip to main content
This guide will walk you through creating your first GRDB application in four simple steps: opening a database connection, defining a schema, creating a record type, and reading/writing data.

Prerequisites

Make sure you’ve installed GRDB in your project before continuing.

Your First GRDB Application

Let’s build a simple player database that stores player names and scores.
1

Import GRDB and Open a Connection

First, import GRDB and create a database connection. You can choose between DatabaseQueue or DatabasePool:
import GRDB

// Option 1: In-memory database (great for testing)
let dbQueue = try DatabaseQueue()

// Option 2: Persistent database file
let dbQueue = try DatabaseQueue(path: "/path/to/database.sqlite")
DatabaseQueue vs DatabasePool: Start with DatabaseQueue for simplicity. It supports in-memory databases and is easier to work with. You can always switch to DatabasePool later for concurrent access in multi-threaded applications.
2

Define Your Database Schema

Create a table to store your data:
try dbQueue.write { db in
    try db.create(table: "player") { t in
        t.primaryKey("id", .text)
        t.column("name", .text).notNull()
        t.column("score", .integer).notNull()
    }
}
GRDB provides a type-safe API for creating tables. Each column is defined with a name and type. Common SQLite types include .text, .integer, .real, and .blob.
3

Define a Record Type

Create a Swift struct that represents a database row. GRDB works seamlessly with Codable types:
struct Player: Codable, Identifiable, FetchableRecord, PersistableRecord {
    var id: String
    var name: String
    var score: Int
    
    // Optional: Define columns for use in queries
    enum Columns {
        static let name = Column(CodingKeys.name)
        static let score = Column(CodingKeys.score)
    }
}
Protocol breakdown:
  • Codable - Enables automatic encoding/decoding
  • Identifiable - Marks id as the primary key
  • FetchableRecord - Enables fetching from database
  • PersistableRecord - Enables saving to database
The Columns enum is optional but recommended. It provides type-safe column references for the query interface.
4

Insert and Query Data

Now you can insert records and query them back:
// Insert records
try dbQueue.write { db in
    try Player(id: "1", name: "Arthur", score: 100).insert(db)
    try Player(id: "2", name: "Barbara", score: 1000).insert(db)
}

// Query records
try dbQueue.read { db in
    // Find a specific player
    let player = try Player.find(db, id: "1")
    print(player?.name ?? "Not found") // "Arthur"
    
    // Get top 10 players
    let bestPlayers = try Player
        .order(Player.Columns.score.desc)
        .limit(10)
        .fetchAll(db)
    
    for player in bestPlayers {
        print("\(player.name): \(player.score)")
    }
    
    // Count all players
    let playerCount = try Player.fetchCount(db)
    print("Total players: \(playerCount)")
}

Complete Example

Here’s the full working example you can copy and paste:
import GRDB

// 1. Open a database connection
let dbQueue = try DatabaseQueue(path: "/path/to/database.sqlite")

// 2. Define the database schema
try dbQueue.write { db in
    try db.create(table: "player") { t in
        t.primaryKey("id", .text)
        t.column("name", .text).notNull()
        t.column("score", .integer).notNull()
    }
}

// 3. Define a record type
struct Player: Codable, Identifiable, FetchableRecord, PersistableRecord {
    var id: String
    var name: String
    var score: Int
    
    enum Columns {
        static let name = Column(CodingKeys.name)
        static let score = Column(CodingKeys.score)
    }
}

// 4. Write and read in the database
try dbQueue.write { db in
    try Player(id: "1", name: "Arthur", score: 100).insert(db)
    try Player(id: "2", name: "Barbara", score: 1000).insert(db)
}

try dbQueue.read { db in
    let player = try Player.find(db, id: "1")
    
    let bestPlayers = try Player
        .order(Player.Columns.score.desc)
        .limit(10)
        .fetchAll(db)
}

Alternative: Using Raw SQL

GRDB also provides direct SQL access if you prefer working with raw queries:
try dbQueue.write { db in
    try db.execute(sql: """
        CREATE TABLE player (
          id TEXT PRIMARY KEY,
          name TEXT NOT NULL,
          score INT NOT NULL)
        """)
}

Understanding Read and Write Access

GRDB enforces a clear distinction between reading and writing:
// Use write blocks for INSERT, UPDATE, DELETE
try dbQueue.write { db in
    var player = Player(id: "1", name: "Arthur", score: 100)
    try player.insert(db)
    
    // Update the score
    player.score += 10
    try player.update(db)
    
    // Delete a player
    try player.delete(db)
}
Thread Safety: GRDB handles all the complexity of multi-threaded database access. You can safely call read and write from any thread.

Common Operations

Here are some common database operations you’ll use frequently:

Update Records

try dbQueue.write { db in
    var player = try Player.find(db, id: "1")!
    player.score += 100
    try player.update(db)
    
    // Or use updateChanges to only update modified fields
    try player.updateChanges { $0.score += 100 }
}

Delete Records

try dbQueue.write { db in
    // Delete a specific player
    try Player.deleteOne(db, id: "1")
    
    // Delete all players with score < 50
    try Player.filter { $0.score < 50 }.deleteAll(db)
}

Filter and Sort

try dbQueue.read { db in
    // Find players named "Arthur"
    let arthurs = try Player
        .filter { $0.name == "Arthur" }
        .fetchAll(db)
    
    // Top 5 players
    let topPlayers = try Player
        .order(Player.Columns.score.desc)
        .limit(5)
        .fetchAll(db)
    
    // Players with score > 500
    let highScorers = try Player
        .filter { $0.score > 500 }
        .order(Player.Columns.name)
        .fetchAll(db)
}

Error Handling

All GRDB methods can throw errors. Make sure to handle them appropriately:
do {
    try dbQueue.write { db in
        try Player(id: "1", name: "Arthur", score: 100).insert(db)
    }
} catch let error as DatabaseError {
    // Handle database-specific errors
    print("Database error: \(error.message ?? "Unknown error")")
} catch {
    // Handle other errors
    print("Error: \(error)")
}

Next Steps

Now that you’ve built your first GRDB application, explore these topics:
Check out the Demo Applications for complete example projects showing GRDB in real-world scenarios.

Build docs developers (and LLMs) love