vapor-docs/3.0/docs/databases/mongodb/basics.md

6.2 KiB

MongoDB Basics

import MongoKitten in your file(s) using MongoKitten. This will automatically also import the BSON APIs.

Connecting to MongoDB

To connect to MongoDB you need to provide your connection URI and the database you want to access. The database will also need access to your worker.

let database = Database.connect(server: "mongodb://localhost:27017", database: "test-db", worker: worker) // Future<Database>

You cannot use the connection globally. If you plan to use this connection globally you'll need to use a lock on it for querying since the connection is not thread safe. This will be added in a later stage of MongoKitten 5's beta.

Connection as a Service

If you want to use the connection within a Vapor 3 route you'll need to register this connection to your Services.

services.register { container -> ClientSettings in
    return "mongodb://localhost:27017" // Your MongoDB URI here
}

services.register { container -> Database in
    let server = try container.make(ClientSettings.self, for: Database.self)

    // Future<Database>
    let db = try Database.connect(sever: server, database: "my-database", worker: container)

    // Await here, it doesn't hurt performant much and it only needs to wait once per worker
    return try db.await(on: container)
}

CRUD

Before applying a CRUD operation you need to select a Collection first. This is the MongoDB equivalent of a table.

You can subscript a database with a string to get a collection with that name. You do not need to set up a schema first.

router.get("users") { request in
    let database = try request.make(Database.self)
    let users = database["users"] // Collection<Document>

    ...
}

Subscripting will give you a Collection<Document>. If you want to read the collection as a different (Codable) type you'll need to map the collection to a different type.

struct User: Codable {
    var _id = ObjectId()
    var username: String
    var age: Int

    init(named name: String, age: Int) {
        self.username = named
        self.age = age
    }
}

let users = database["users"].map(to: User.self)

CRUD operations

Insert

Inserting entities is done by using .insert with either one or a sequence of the entity.

users.insert(user)
users.insertAll([user1, user2, user3])

Insert returns a Future<Reply.Insert> which you can use to determine if one (or more) inserts failed. Is reply.ok != 1 the future will not be completed but failed with the reply, instead.

let reply = users.insertAll([user1, user2, user3])

reply.do { success in
    print("\(success.n) users inserted")
}.catch { error in
    // Insert failed!
}

Find

Using find on a collection returns a Cursor<C> where C is the type nested in the collection as demonstrated above.

find can take 4 arguments. A filter, range, sort and projection.

There is also findOne, which is useful when you want to exactly one object. This does not support a range expression.

Filter

The filter is a MongoKitten Query object.

// User?
guard let joannis = users.findOne("username" == "Joannis" && "active" == true) else {
    return
}

Range

A range is any native swift Range. It is used for skip and limit.

// Cursor with the first 10 users

let users1 = users.find(in: ..<10) // Cursor<User>
let users2 = users.find(in: 10..<20) // Cursor<User>
let users3 = users.find(in: 30...) // Cursor<User>

Sort

The Sort is used to sort entities in either ascending or descending order based on one or more fields.

let maleUsers = users.find("gender" == "male", sortedBy: ["age": .ascending]) // Cursor<User>

Projection

The projection ensures only the specifies subset of fields are returned. Projections can also be computed fields. Remember that the projection must fit within the Collection's Codable type. If this is not a dictionary(-like) type such as Document it will be required that the Document fetched matches the Codable type.

let adults = users.find("age" >= 21, projecting: ["_id": .excluded, "username": .included]) // Cursor<User>

Count

Count is similar to a find operation in that it is a read-only operation. Since it does not return the actual data (only an integer of the total entities found) it does not support projections and sorting. It does support a range expression, although it's not often used.

let totalUsers = users.count() // Future<Int>
let femaleUsers = users.count("gender" == "female") // Future<Int>

Update

Updating can be done on one or all entities. Updates can either update the entire entity or only a subset of fields.

let user = User(named: "Joannis", age: 111)

users.update("username" == "Joannis", to: user)

Update also indicates a success state in the same way insert does.

Updating fields

Updating a subset of fields can be done more efficiently using

// Rename `Joannis` -> `JoannisO`
users.update("username" == "Joannis", fields: [
    "username": "JoannisO"
])

// Migrate all users to require a password update
users.update(fields: [
    "resetPassword": true
])

Upsert

If you don't know if an entity exists but want it inserted/updated accordingly you should use upsert.

let user = User(named: "Joannis", age: 111)

users.upsert("_id" == user._id, to: user)

Remove

Remove removes the first or all entities matching a query.

// Remove me!
users.remove("username" == "Joannis")

// Remove all Dutch users
users.removeAll("country" == "NL")

Troubleshooting

Ambiguous naming

In some situations you may find that MongoKitten's Database or ClientSettings are ambiguous with another library. The following lines will help get rid of that.

typealias MongoDB = MongoKitten.Database
typealias MongoDBSettings = MongoKitten.ClientSettings

In the above case you'll have to use the aliases instead of the normal references to Database and/or ClientSettings. Alternatively you can prefix the occurences of those instances with MongoKitten., indicating you want the Database object of the MongoKitten module.