Skip to main content
GRDB is a toolkit for SQLite databases with a focus on application development. This guide explains the philosophy behind GRDB and why it could become your favorite database library.

Core Principles

Record Types Over Raw Rows

It’s generally easier to access a database through “record types” rather than dealing with raw database rows.
player.name // ✓ Natural and type-safe
playerRow["name"] // Less intuitive
GRDB records are plain Swift values—no auto-updating, no uniquing, no lazy loading. This design embraces:
  • Immutability: Records can be value types (structs)
  • Memory safety: No hidden state or side effects
  • Multi-threading safety: Values can cross threads freely
GRDB belongs to a new generation of ORMs like Rust’s Diesel, where records behave as simple values instead of managed objects.

Raw SQL When You Need It

Complex queries sometimes need to be prototyped in an SQL shell. Once perfected, translating them to a query builder can be painful with no guarantee of success. GRDB lets you use raw SQL whenever it makes sense:
// Query interface
let bestPlayers = try Player
    .order(\.score.desc)
    .limit(10)
    .fetchAll(db)

// Raw SQL - equally supported
let bestPlayers = try Player.fetchAll(db, sql: """
    SELECT * FROM player ORDER BY score DESC LIMIT 10
    """)

The Database as Single Source of Truth

Unlike Core Data (with multiple managed object contexts) or Realm (with per-thread databases), GRDB assumes apps want a reliable and unambiguous storage:
  • One truth: The database file is authoritative
  • Explicit fetches: Records are snapshots, not live objects
  • Clear updates: Changes go through explicit save operations
Fetched records behave like an in-memory cache. Your app decides whether to ignore future changes or observe them with ValueObservation.

Focus on Application Developers

GRDB puts all bets on SQLite and front-end GUI applications:
  • ✓ iOS, macOS, watchOS, tvOS
  • ✓ Migrations, database observation, concurrency safety
  • ✓ SwiftUI and Combine support
  • ✗ Servers, MySQL, or PostgreSQL (out of scope)

Solving Real Problems

Any Swift Type Can Be a Record

Most libraries require you to subclass a root class (NSManagedObject, Realm.Object). This prevents using Swift structs and value types. With GRDB, any type can become a record:
struct Place {
    var id: Int64?
    let title: String
    let coordinate: CLLocationCoordinate2D
}

// Add protocols to gain persistence abilities
extension Place: FetchableRecord { } // Load from database
extension Place: TableRecord { }      // Generate SQL queries  
extension Place: PersistableRecord { } // Save to database
Being protocol-oriented and welcoming immutable types, GRDB records don’t auto-update or enforce uniquing.

Records That Cross Threads

Immutable value records can safely cross threads. No need to pass object IDs like Core Data or Realm:
if let player = try Player.fetchOne(db, key: 1) {
    DispatchQueue.main.async {
        nameField.text = player.name // ✓ Safe
    }
}

Database Change Notifications

Instead of auto-updating records, GRDB provides ValueObservation to reactively track database changes:
// Define what to observe
let observation = ValueObservation.tracking { db in
    try Player.fetchAll(db)
}

// Start observing
let cancellable = observation.start(in: dbQueue) { players in
    print("Fresh players: \(players)")
}
Guarantees:
  • Notifications only for committed changes (saved on disk)
  • Changes notified in transaction order
  • All changes detected: record methods, raw SQL, triggers, foreign key cascades

Non-Blocking Reads

DatabasePool enables concurrent reads, so long-running writes don’t block UI queries:
let dbPool = try DatabasePool(path: dbPath)

// Concurrent reads don't block each other
try dbPool.read { db in
    // UI query
}

Concurrency Comparison

How different libraries handle multi-threading challenges:
LibraryConcurrent WritesIsolationConflictsBlocked UI
FMDB (FMDatabase)App handlesApp handlesApp handlesApp handles
FMDB (FMDatabaseQueue)App handlesBlocks
SQLite.swiftApp handlesApp handlesApp handles
Core DataApp handlesDifficultApp handles
GRDB (DatabaseQueue)App handlesBlocks
GRDB (DatabasePool)App handles✓ (unless max readers reached)
Conflicts (same data edited concurrently) are always the app’s responsibility. GRDB provides tools but doesn’t enforce policies.

Never Pay for Using Raw SQL

GRDB’s query interface generates SQL, but you can always drop to raw SQL:
// Query interface
try Player.order(\.score.desc).limit(10).fetchAll(db)

// Raw SQL - equally supported
try Player.fetchAll(db, sql: "SELECT * FROM player ORDER BY score DESC LIMIT 10")
SQL interpolation provides safety without verbosity:
let score = 1000
let id = 42
try db.execute(literal: "UPDATE player SET score = \(score) WHERE id = \(id)")
Custom SQL works everywhere:
  • Direct execution
  • Record fetching
  • Query interface mixing
  • Database observation (ValueObservation, Combine, RxSwift)

Performance and Flexibility

For performance-critical code, access raw rows with lazy cursors:
let rows = try Row.fetchCursor(db, sql: "SELECT id, name, score FROM player")
while let row = try rows.next() {
    let id: Int64 = row[0]
    let name: String = row[1]
    let score: Int = row[2]
}

Get Started

Installation

Add GRDB to your project

Quick Start

Build your first GRDB app

Demo Apps

Explore example applications

Best Practices

Design patterns and recommendations
The Concurrency Guide provides detailed information about GRDB’s multi-threading guarantees.

Build docs developers (and LLMs) love