CRUD Operations

CRUD operations are four broad categories of database operations.

CRUD.tutorial

Create, Read, Update and Delete. These are represented in MongoDB as Insert, Find, Update and Delete.

While there are more specific types alternatives to Find and Update, we’ll stick to the basics for now.

Create

Insert operations create new entities. When an entity is created, it is uniquely identifier by it’s _id field. This is a mandatory key, therefore always present in your models. If you omit this key, one will be generated for you.

  1. The first step in inserting an entity, is getting the database handle. This has already been described in previous tutorials, so visit those if you’re unsure how to get a database handle.

    Create.swift
    import MongoKitten
    
    // Replace this as appropriate
    let connectionString = "mongodb://localhost/my_database"
    
    let db = try await MongoDatabase.connect(to: connectionString)
    let kittens = db["kittens"]
    CRUD-01-Insert-Setup.swift
  2. Next, define a Kitten model, containing an identifer, name and age. Note that MongoKitten natively supports Date, and a new type - ObjectId.

    Create.swift
    import MongoKitten
    
    // Replace this as appropriate
    let connectionString = "mongodb://localhost/my_database"
    
    let db = try await MongoDatabase.connect(to: connectionString)
    let kittens = db["kittens"]
    
    import struct Foundation.Date
    
    struct Kitten: Codable {
        let _id: ObjectId
        let name: String
        let age: Date
    }
    CRUD-02-Insert-Model.swift
  3. Create a new Kitten, calling the ObjectId initializer to generate a new id for your Kitten instance.

    Create.swift
    import MongoKitten
    
    // Replace this as appropriate
    let connectionString = "mongodb://localhost/my_database"
    
    let db = try await MongoDatabase.connect(to: connectionString)
    let kittens = db["kittens"]
    
    import struct Foundation.Date
    
    struct Kitten: Codable {
        let _id: ObjectId
        let name: String
        let age: Date
    }
    
    let kitten = Kitten(
        _id: ObjectId(),
        name: "Milo",
        age: Date(timeIntervalSince1970: 1617540152)
    )
    CRUD-03-Insert-CreateInstance.swift
  4. Finally, perform an insertEncoded operation. This encodes your Codable model to BSON and inserts it into the database.

    Create.swift
    import MongoKitten
    
    // Replace this as appropriate
    let connectionString = "mongodb://localhost/my_database"
    
    let db = try await MongoDatabase.connect(to: connectionString)
    let kittens = db["kittens"]
    
    import struct Foundation.Date
    
    struct Kitten: Codable {
        let _id: ObjectId
        let name: String
        let age: Date
    }
    
    let kitten = Kitten(
        _id: ObjectId(),
        name: "Milo",
        age: Date(timeIntervalSince1970: 1617540152)
    )
    
    try await kittens.insertEncoded(kitten)
    CRUD-04-Insert-Perform.swift

Read

Find operations retrieve entities from the database. You can perform a find operation on a collection, or a single document.

First, we’ll try to retrieve a single entity - the one we just inserted.

  1. The findOne operation allows you to read one entity from the collection, matching your query.

    MongoKitten has its own operator overloads for helping you build readable queries.

    Read.swift
    struct Kitten: Codable {
        let _id: ObjectId
        let name: String
        let age: Date
    }
    
    let kitten = Kitten(
        _id: ObjectId(),
        name: "Milo",
        age: Date(timeIntervalSince1970: 1617540152)
    )
    
    try await kittens.insertEncoded(kitten)
    
    func getKitten(byId id: ObjectId) async throws {
        guard let kitten = try await kittens.findOne("_id" == id, as: Kitten.self) else {
            struct KittenNotFound: Error {}
            throw KittenNotFound()
        }
    
        return kitten
    }
    
    let kittenCopy = try await getKitten(byId: kitten._id)
    CRUD-05-Find-Instance.swift
  2. You can also find all entities matching your query. You can provide a query, to filter entities of interest. Omitting a filter will return all kittens.

    You’ll need to decode kittens manually here. You can do so using the decode() function.

    Read.swift
    struct Kitten: Codable {
        let _id: ObjectId
        let name: String
        let age: Date
    }
    
    let kitten = Kitten(
        _id: ObjectId(),
        name: "Milo",
        age: Date(timeIntervalSince1970: 1617540152)
    )
    
    try await kittens.insertEncoded(kitten)
    
    func getKitten(byId id: ObjectId) async throws {
        guard let kitten = try await kittens.findOne("_id" == id, as: Kitten.self) else {
            struct KittenNotFound: Error {}
            throw KittenNotFound()
        }
    
        return kitten
    }
    
    let kittenCopy = try await getKitten(byId: kitten._id)
    
    // Loop over all kittens
    for try await kitten in kittens.find().decode(Kitten.self) {
        print(kitten)
    }
    CRUD-06-Find-Many.swift
  3. If you’re only interested in the kittens’ names, you can use the map function to lazily transform each value.

    The query will be executed on demand. Therefore, the cursor only executes once the first result is requested.

    Read.swift
    struct Kitten: Codable {
        let _id: ObjectId
        let name: String
        let age: Date
    }
    
    let kitten = Kitten(
        _id: ObjectId(),
        name: "Milo",
        age: Date(timeIntervalSince1970: 1617540152)
    )
    
    try await kittens.insertEncoded(kitten)
    
    func getKitten(byId id: ObjectId) async throws {
        guard let kitten = try await kittens.findOne("_id" == id, as: Kitten.self) else {
            struct KittenNotFound: Error {}
            throw KittenNotFound()
        }
    
        return kitten
    }
    
    let kittenCopy = try await getKitten(byId: kitten._id)
    
    // Loop over all kittens
    for try await kitten in kittens.find().decode(Kitten.self) {
        print(kitten)
    }
    
    // Loop over all kittens' names
    let lastYear = Date().addingTimeInterval(-(24 * 3600 * 365))
    let kittenNames = kittens.find("age" >= lastYear)
        .decode(Kitten.self)
        .map(\.name)
    for try await name in kittenNames {
        print(name)
    }
    CRUD-07-Find-Transform.swift

Update

There are a couple of Update operations. Update queries can affect part of the model, using the $set operator.

Alternatively, they can replace the whole model.

  1. First, start the query by calling updateMany or updateOne, where updateOne limits amount of updates that can take place to one.

    Update.swift
    try await users.updateMany(
        where: "role" == "trial",
    CRUD-08-Update-Set.swift
  2. Next, we’ll need to specify the filter. Only documents that match this filter will be updated.

    Update.swift
    try await users.updateMany(
        where: "role" == "trial"
    CRUD-09-Update-SetQuery.swift
  3. Now, for updating part of a Document, you can use the setting and unset arguments. In setting, only fields that are specified will be updated to their new value. You can use MongoDB’s projection or update operators to perform more advanced operations.

    See the Official Documentation for more information.

    Update.swift
    try await users.updateMany(
        where: "role" == "trial",
        setting: [
            "active": false
        ]
    )
    CRUD-10-Update-SetDocument.swift
  4. You can also replace the document as a whole, allowing you to change all values in the model at once.

    Update.swift
    try await users.updateEncoded(
        where: "_id" == kitten._id,
        to: kitten
    )
    CRUD-11-Update-Replace.swift