Introduction: Why Does a Swift Developer Need Databases?
When developing native applications for iOS and macOS, there is often a need to store data not only locally (Core Data, UserDefaults) but also on a remote server. This could be server analytics, logging, synchronization between devices, or a full-fledged backend for the application. The two most popular types of databases are relational (PostgreSQL) and document-oriented (MongoDB).
In this article, we will look at how to connect to PostgreSQL and MongoDB from a Swift application using popular libraries and drivers. Special attention will be paid to practice: installing dependencies, writing queries, and error handling.
1. Working with PostgreSQL from Swift
PostgreSQL is a powerful relational DBMS, ideal for projects where data integrity and complex SQL queries are important. In Swift, we can work with PostgreSQL in two main ways:
- Through the Fluent ORM (part of the Vapor framework) — a high-level approach, convenient for web applications.
- Directly via a driver (e.g.,
PostgresNIO) — low-level control, suitable for iOS/macOS applications.
Let's consider the second option, as it does not require Vapor and is suitable for regular applications.
1.1. Connecting to PostgreSQL via PostgresNIO
Add the dependency to Package.swift:
dependencies: [ .package(url: "https://github.com/vapor/postgres-nio.git", from: "1.18.0")]Example of connecting and executing a query:
import PostgresNIOimport NIOCoreimport NIOPosix
func queryPostgres() async throws { let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) defer { try? eventLoopGroup.syncShutdownGracefully() }
let config = PostgresConnection.Configuration( host: "localhost", port: 5432, username: "swift_user", password: "secret", database: "test_db", tls: .disable )
let connection = try await PostgresConnection.connect( on: eventLoopGroup.next(), configuration: config ) defer { try? connection.close() }
let rows = try await connection.query("SELECT id, name FROM users LIMIT 5;", logger: nil) for try await row in rows { let id = try row.decode(Int.self, column: "id") let name = try row.decode(String.self, column: "name") print("ID: \\(id), Name: \\(name)") }}This code establishes a connection to a local PostgreSQL server, executes a SELECT, and prints the results. Important: never store passwords in code — use environment variables or Keychain.
1.2. Inserting Data with Parameters
To protect against SQL injection, use parameterized queries:
let name = "Alice"let age = 30try await connection.query( "INSERT INTO users (name, age) VALUES ($1, $2);", params: [.string(name), .int(age)], logger: nil)2. Working with MongoDB from Swift
MongoDB is a NoSQL database that stores data in JSON-like documents (BSON). For Swift, there is an excellent driver MongoSwift.
2.1. Installing MongoSwift
Add to Package.swift:
dependencies: [ .package(url: "https://github.com/mongodb/mongo-swift-driver.git", from: "1.3.0")]2.2. Connection and CRUD Operations
Example of connecting to a local MongoDB and inserting a document:
import MongoSwift
func mongoExample() async throws { let client = try MongoClient("mongodb://localhost:27017") defer { try? client.syncClose() }
let db = client.db("test_database") let collection = db.collection("users")
// Inserting a document let document: Document = ["name": "Bob", "age": 25, "city": "Moscow"] let result = try await collection.insertOne(document) print("Inserted document with id: \\(result?.insertedID ?? "")")
// Reading documents let query: Document = ["name": "Bob"] let documents = try await collection.find(query).toArray() for doc in documents { print(doc) }}Conclusion
We have covered the basics of working with PostgreSQL and MongoDB from Swift. Both databases have their strengths: PostgreSQL is ideal for structured data with complex relationships, while MongoDB is great for flexible schemas and rapid prototyping. Choose the tool that best suits your project's tasks.
In the next article, we will look at more advanced topics: connection pooling, migrations, and working with transactions.