# MongoKitten: a pure Swift MongoDB driver for server side Swift

> MongoKitten is a MIT licensed MongoDB driver written in Swift on top of Swift NIO, built for asynchronous server side use and paired with the Meow ORM. It suits Swift backend teams on Vapor or Hummingbird who want a non-blocking driver without a C dependency, and it expects you to think about connection timing at boot.

**orlandos-nl/MongoKitten** — Native MongoDB driver for Swift, written in Swift

- Repository: https://github.com/orlandos-nl/MongoKitten
- Stars: 756 · Forks: 113
- Language: Swift
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/orlandos-nl-mongokitten

## A driver that stays inside Swift and never blocks a thread

MongoKitten describes itself as a fast, pure Swift MongoDB driver based on Swift NIO and built for server side Swift, supporting MongoDB in server and embedded environments. The pure Swift part is the distinguishing feature against drivers that wrap a C client library through a system dependency.

The asynchrony is the other half. The README states that the driver is fully asynchronous, which means it does not block any threads, and that this makes it usable in any asynchronous environment such as Vapor or Hummingbird. Both of those are Swift server frameworks built around async/await, so a driver that returns async functions and never parks a thread fits them without an adapter layer.

The requirements are stated plainly: Swift 5.5 or later for async/await support, MongoDB 3.6 or later, and the platforms macOS, iOS and Linux. Two features have stricter conditions. Change streams need MongoDB 3.6 or later on a replica set or sharded cluster, and transactions need MongoDB 4.0 or later on a replica set or sharded cluster. That second pair of requirements is where most of the deployment planning goes, since a standalone mongod supports neither.

The audience is a Swift backend team that already lives in an async environment and wants MongoDB access without a native dependency in the build.

## connect and lazyConnect make you choose at boot time

The driver offers two ways to obtain a database handle, and the README devotes a section to the difference because it changes how your service fails.

A connect call is async throws and immediately attempts to establish a connection, throwing an error if it cannot. The stated advantage is that a booted server is then known to have a connection, so any problem with MongoDB shows up immediately and the error is easy to inspect.

A lazyConnect call is throws and defers establishing the connection until it is necessary, so the only errors thrown at that point are unusable credentials. The README argues for it during development, because connecting to MongoDB can be slow in some setups and lazyConnect lets you start working almost immediately. It also keeps cluster outages or badly timed topology changes from affecting application boot, with the driver attempting to recover in the background.

The cost is stated just as plainly: if something does go wrong with a lazy connection, it can be hard to debug. For a production service that must not serve traffic without a database, connect is the safer default. For a development machine, or a service that can degrade while the database recovers, lazyConnect is defensible.

## Adding the package and running your first query

MongoKitten uses Swift Package Manager. The dependency goes into Package.swift, and the product has to be added to the target as well:

```swift
.package(url: "https://github.com/orlandos-nl/MongoKitten.git", from: "7.9.0")
```

```swift
.product(name: "MongoKitten", package: "MongoKitten"),
```

The Meow ORM lives in the same package and is added as a separate product if you want it:

```swift
.product(name: "Meow", package: "MongoKitten"),
```

With the package in place, connecting and doing the first insert and query takes a handful of lines:

```swift
// In your code
import MongoKitten

// Connect to database
let db = try await MongoDatabase.connect(to: "mongodb://localhost/my_database")

// Insert a document
try await db["users"].insert(["name": "Alice", "age": 30])

// Query documents
let users = try await db["users"].find("age" >= 18).drain()
```

Note that collection access is subscript based, so db["users"] is the users collection, and the query filter uses operator syntax rather than a dictionary. The drain call consumes the cursor and returns the results as an array.

## Cursors and Codable decoding without a schema layer

Results come back through a cursor, and the README walks through three things about it: fetching results, and that cursors are generic.

The generic part is what makes the driver pleasant with Codable. The quickstart decodes straight into your own type:

```swift
// Use with Codable
struct User: Codable {
    let name: String
    let age: Int
}

let typedUsers = try await db["users"]
    .find()
    .decode(User.self)
    .drain()
```

Because the cursor carries the decoded type, the result of a find is typed end to end instead of a collection of loosely typed documents that you convert later. That is the main argument for using the driver directly rather than reaching for the ORM, and Meow is there when you want a model layer anyway.

The README also spends a section on BSON itself, covering literals and what it calls Just Another Collection, with a warning to think twice before converting between Document and Dictionary. That caution is the practical trap for Swift developers, since BSON has types that do not map onto Swift's type system one to one, and a conversion that looks free can quietly lose information or change a numeric type. The README treats this as a section of its own, which is a fair signal of where the friction is.

## Transactions and change streams need a replica set

If you need multi-document transactions or change streams, the deployment shape is decided for you. Transactions require MongoDB 4.0 or later on a replica set or a sharded cluster, and change streams require MongoDB 3.6 or later on a replica set or a sharded cluster.

The repository ships a docker-compose file that shows the intended local setup, and it is a three member replica set rather than a single container:

```yaml
services:
  mongo-1:
    image: mongo:5.0
    ports:
      - "27017:27017"
    container_name: mongo-1
    hostname: mongo-1
    networks: 
      - mongo_cluster
    command: mongod --replSet rs0
```

The other two members follow the same pattern on ports 27018 and 27019, each declaring a dependency on the previous one, and a fourth service called mongosetup runs a script from the mounted scripts directory to initialise the set. The compose file also defines a bridge network named mongo_cluster for the nodes to talk over.

The practical takeaway is that local development for transaction features needs a replica set, and the repository gives you one to copy rather than leaving you to configure it. The compose file pins mongo:5.0, and the README separately notes that a server is needed for development anyway, whether local or a hosted one such as MongoDB Atlas.

## Vapor users register the database as a service

If you use Vapor, the driver expects a specific registration pattern, and the README shows it in full. The database handle is stored in the application storage under a StorageKey, and exposed through an extension on Application:

```swift
private struct MongoDBStorageKey: StorageKey {
    typealias Value = MongoDatabase
}

extension Application {
    public var mongo: MongoDatabase {
        get {
            storage[MongoDBStorageKey.self]!
        }
        set {
            storage[MongoDBStorageKey.self] = newValue
        }
    }
    
    public func initializeMongoDB(connectionString: String) throws {
        self.mongo = try MongoDatabase.lazyConnect(to: connectionString)
    }
}
```

A second extension exposes the database on the Request, and here the log metadata matters: the mongo property returns application.mongo.adoptingLogMetadata with the request id attached as a string. Every query made during that request then carries the request id in its log metadata, which is what makes request scoped tracing possible with a database driver.

The initialization itself is separate from the rest, and the README is emphatic about the ordering: instantiate the database driver before starting your application.

```swift
try app.initializeMongoDB(connectionString: "mongodb://localhost/my-app")
```

## Meow, the ORM in the same package

Meow is described simply as an ORM that resides in the same package, so adopting it does not mean adding another dependency, only another product to your manifest.

The README has separate sections for setting Meow up with Vapor and with Hummingbird, which tells you the ORM is designed around framework integration rather than being framework agnostic. There are sections for models, for queries, and for references, the last of which is the part that matters for anyone modelling a document graph, since MongoDB can hold references between documents and Meow exposes that rather than flattening everything into nested values.

The choice between the driver and the ORM is the real decision. The driver gives you typed cursors and Codable decoding with nothing between you and the server. Meow adds models, queries and references, which pays off when you have a domain with relationships, and costs you a layer whose conventions you have to learn.

Other projects built on the driver are listed as well, including MongoQueue, Vapor's Fluent MongoDB driver and the MongoDB queues driver for Vapor, which suggests the driver is used as a foundation rather than only as an application dependency.

## MIT licensed, frequent releases, and a quiet last few months

The licence is MIT, with the terms in LICENSE.md at the repository root. The repository also carries CONTRIBUTING.md, a CODE_OF_CONDUCT.md, CODEOWNERS, CONTRIBUTORS.md and SECURITY.md, and a .spi.yml file for Swift Package Index publication.

Releases are frequent and numbered to two decimals. The recent list shows 7.16.3 on 2026-05-20, 7.16.2 on 2026-05-16 and 7.16.1 on 2026-04-26, and the repository was last pushed on 2026-05-20. It is not archived.

The version requirement in the quickstart is worth noticing against that history. The manifest example asks for from: "7.9.0", which is a floor rather than a pin, so a fresh resolve takes the newest release in the 7.x range, while an existing project that has not touched its manifest stays on an older build. Given that the documented floor is several patch releases behind the latest tag, anyone reading the quickstart today is being pointed at a version that already has newer releases behind it.

For a driver, the upgrade path is a normal semver one, but the practical risk is server side. A driver release that changes how a query is encoded or how BSON values are decoded will show up as data differences in production, so a staged upgrade against a copy of your own data is the sensible first step, and the docker-compose replica set in the repository is a reasonable place to run it.

## Conclusion

MongoKitten fits a Swift backend team that wants an asynchronous MongoDB driver without leaving the language, and that already runs a replica set or sharded cluster if it needs transactions or change streams. It does not fit a project that has to support MongoDB older than 3.6 or Swift older than 5.5, and it does not fit an application that must fail fast on a broken database while still using lazyConnect. Before adopting it, decide explicitly between connect and lazyConnect for your boot path, and read the Document versus Dictionary guidance in the README before converting BSON between your own types.

## FAQ

### How do I add MongoKitten to a Swift project?

Add `.package(url: "https://github.com/orlandos-nl/MongoKitten.git", from: "7.9.0")` to your Package.swift dependencies and also add the product with `.product(name: "MongoKitten", package: "MongoKitten")` for your target. Meow, the ORM in the same package, is added as its own product.

### What is the difference between connect and lazyConnect in MongoKitten?

A connect call is async throws and establishes the connection immediately, throwing if it fails, so a booted server is known to have a working connection. A lazyConnect call is throws and defers the connection until it is needed, which keeps application boot fast and lets the driver recover from outages, at the cost of harder debugging when something goes wrong.

### What are the requirements for MongoKitten transactions and change streams?

Transactions need MongoDB 4.0 or later, and change streams need MongoDB 3.6 or later, and both require a replica set or a sharded cluster rather than a standalone server.

### Can I use MongoKitten with an ORM?

Yes, Meow is an ORM that resides in the same package and is added as its own Swift Package Manager product. The README documents separate setup paths for it with Vapor and with Hummingbird.

## Sources

- [Issues](https://github.com/orlandos-nl/MongoKitten/issues)
- [License: MIT](https://github.com/orlandos-nl/MongoKitten/blob/main/LICENSE)
- [orlandos-nl/MongoKitten on GitHub](https://github.com/orlandos-nl/MongoKitten)
- [README](https://github.com/orlandos-nl/MongoKitten/blob/main/README.md)
- [Releases](https://github.com/orlandos-nl/MongoKitten/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/orlandos-nl-mongokitten
