Get started with GRDB in minutes by building your first SQLite database application
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.
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 recordstry 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 recordstry 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)")}
Here’s the full working example you can copy and paste:
import GRDB// 1. Open a database connectionlet dbQueue = try DatabaseQueue(path: "/path/to/database.sqlite")// 2. Define the database schematry 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 typestruct 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 databasetry 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)}
GRDB enforces a clear distinction between reading and writing:
// Use write blocks for INSERT, UPDATE, DELETEtry 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.
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 }}
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)}
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)}