Skip to content

Guide · Open source

How to use SQLite.swift: setup, queries and tips

Updated · written with AI from the project's own documentation and checked against our catalogue data

SQLite.swift is an open-source Swift library that wraps SQLite3, the small database engine built into many apps and devices. This guide is for developers who store data locally in an app and want to write queries in Swift instead of plain SQL strings. By the end you will know how to add the library, define a table, insert, read, update and delete rows, and handle errors.

SQLite.swiftstephencelis/SQLite.swift · ★ 10.2k · MIT

Quick start

  1. 1Add the package. Add the SQLite.swift .package entry with from: "0.17.0" to the dependencies list in Package.swift.
  2. 2Build the project. Run swift build to fetch and compile the dependency.
  3. 3Open a connection. Create a Connection with a path to your SQLite database file inside a do...catch block.
  4. 4Define the table and columns. Create a Table and typed Expression values for each column, then run the create statement.
  5. 5Insert and query rows. Use insert with the <- operator, then iterate db.prepare(table) to read rows.
  6. 6Update or delete with filters. Filter the table with a Swift expression, then call update or delete on the filtered query.
  7. 7Handle errors in reads. Use prepareRowIterator(_:) when a read may encounter lock or I/O errors so the app can catch them.

What SQLite.swift does and when to use it

Instead of writing SQL as text, you describe tables and queries using Swift code, and the compiler can catch some mistakes before the app runs. You can still run raw SQL statements directly when you need to.

It is aimed at Swift developers building iOS, macOS or other Apple-platform apps that store data locally in SQLite, and at developers who want compile-time checks on their queries.

  • Good fit: Apple-platform apps with local SQLite storage, teams that use Swift Package Manager, projects that need SQLCipher encryption via Swift Package Manager.
  • Poor fit: teams wanting an async-first or ORM-style API, projects needing full Linux support without caveats (the README says Linux works with some limitations), or anyone wanting a non-Swift database client.

Install SQLite.swift with Swift Package Manager

The README recommends Swift Package Manager. Add the library as a dependency in your Package.swift file, then build your project.

If you use Xcode rather than Swift Package Manager, the README also describes a manual path: add the SQLite.xcodeproj file as a sub-project, link the SQLite.framework for your platform, and embed it when running on a device.

  • Prerequisites: a Swift toolchain with Swift Package Manager, and Xcode for Apple-platform projects.
  • The README points to the Tests/SPM folder in the repository as a small demo project that uses SPM.
.package(url: "https://github.com/stephencelis/SQLite.swift.git", from: "0.17.0") $ swift build

Open a database connection and create a table

Start by opening a Connection to a file path. Table, Expression and column definitions are plain Swift values. Wrap database calls in do...catch so errors are handled.

Use SQLite.Expression when you also import SwiftUI, to avoid a name clash with SwiftUI.Expression.

do { let db = try Connection("path/to/db.sqlite3") let users = Table("users") let id = SQLite.Expression<Int64>("id") let name = SQLite.Expression<String?>("name") let email = SQLite.Expression<String>("email") try db.run(users.create { t in t.column(id, primaryKey: true) t.column(name) t.column(email, unique: true) }) } catch { print(error) }

Insert, read, update and delete rows

Inserts use the <- operator to pair a column with a value. Reading with db.prepare(users) returns rows you can index by column. Updates and deletes are built from a filtered table, so the WHERE clause comes from the Swift expression.

The README shows the generated SQL in comments next to each call, which is useful for checking what runs.

  • Insert: users.insert(name <- "Alice", email <- "alice@mac.com"), then db.run(insert).
  • Read: for user in try db.prepare(users) { ... }.
  • Update: alice.update(email <- email.replace("mac.com", with: "me.com")).
  • Delete: alice.delete().
  • Count: db.scalar(users.count).
let insert = users.insert(name <- "Alice", email <- "alice@mac.com") let rowid = try db.run(insert) for user in try db.prepare(users) { print("id: \(user[id]), name: \(user[name]), email: \(user[email])") } let alice = users.filter(id == rowid) try db.run(alice.update(email <- email.replace("mac.com", with: "me.com"))) try db.run(alice.delete())

Use raw SQL when you need it

SQLite.swift also works as a lightweight wrapper over the C API. You can prepare a statement from a SQL string, bind values with ? placeholders, and read rows by column index.

The README notes that db.totalChanges, db.changes and db.lastInsertRowid are available for checking results of writes.

let stmt = try db.prepare("INSERT INTO users (email) VALUES (?)") for email in ["betty@icloud.com", "cathy@icloud.com"] { try stmt.run(email) } for row in try db.prepare("SELECT id, email FROM users") { print("id: \(row[0]), email: \(row[1])") }

Handle errors safely when reading rows

Iterating the sequence returned by prepare(_:) force-tries each step. If SQLite reports an error partway through, such as SQLITE_BUSY while another connection holds a lock, the app crashes instead of throwing.

To handle these errors, read rows with prepareRowIterator(_:) instead. The documentation has a section on failable iteration.

  • Use prepareRowIterator(_:) for reads that may hit lock or I/O errors.
  • Keep do...catch around every database call.

What people use it for

  • Storing a local user list with unique email addresses in an iOS or macOS app.
  • Updating matching rows, such as replacing an email domain, without hand-writing the SQL string.
  • Running a count query on a table to show totals in the interface.
  • Inserting many rows with a reusable prepared statement using ? placeholders.
  • Building a local data layer for an Apple-platform app that also needs SQLCipher encryption via Swift Package Manager.

Frequently asked questions

Is SQLite.swift free to use?

Yes. The README says SQLite.swift is available under the MIT license. See the LICENSE file in the repository for the full terms.

How do I install SQLite.swift?

Add the package to the dependencies in your Package.swift with the URL https://github.com/stephencelis/SQLite.swift.git and from: "0.17.0", then run swift build. The README also describes a manual Xcode sub-project install.

Does SQLite.swift work on Linux?

The README says it works on Linux with some limitations. Check the Linux documentation in the repository before relying on it for a Linux project.

What are the alternatives to SQLite.swift?

The README lists GRDB and SQLiteDB as other Swift wrappers, and mentions FMDB as an Objective-C option. Compare them on the API style and platform support you need.

Does SQLite.swift support encryption?

The README lists SQLCipher support via Swift Package Manager. Check the documentation for setup details.

Why does my app crash when reading rows?

Iterating prepare(_:) force-tries each step, so errors such as SQLITE_BUSY crash the app. Use prepareRowIterator(_:) to handle those errors instead.

New agent skills, repos and AI tools — every week

The best new skills and open-source projects, the fastest-growing repos, and one free prompt. Five minutes to read. Unsubscribe anytime.

Our verdict

SQLite.swift suits Swift developers building Apple-platform apps that store data in SQLite and want compile-time checks on queries. Pick another option if you want an async-first or ORM-style API, full Linux support without caveats, or a non-Swift client; the README points to GRDB and SQLiteDB as alternatives.

Install options & downloads