Insert multiple records into database with Vapor3

Viewed 801

I want to be able to bulk add records to a nosql database in Vapor 3.

This is my Struct.

struct Country: Content {

   let countryName: String
   let timezone: String
   let defaultPickupLocation: String

}

So I'm trying to pass an array of JSON objects but I'm not sure how to structure the route nor how to access the array to decode each one.

I have tried this route:

    let countryGroup = router.grouped("api/country")

    countryGroup.post([Country.self], at:"bulk", use: bulkAddCountries)

with this function:

 func bulkAddCountries(req: Request, countries:[Country]) throws ->  Future<String> {
    for country in countries{
    return try req.content.decode(Country.self).map(to: String.self) { countries in



        //creates a JSON encoder to encode the JSON data
        let encoder = JSONEncoder()
        let countryData:Data
        do{
            countryData = try encoder.encode(country) // encode the data
        } catch {
            return "Error. Data in the wrong format."
        }
        // code to save data
    }
    }
}

So how do I structure both the Route and the function to get access to each country?

2 Answers

I'm not sure which NoSQL database you plan on using, but the current beta versions of MongoKitten 5 and Meow 2.0 make this pretty easy.

Please note how we didn't write documentation for these two libraries yet as we pushed to a stable API first. The following code is roughly what you need with MongoKitten 5:

// Register MongoKitten to Vapor's Services
services.register(Future<MongoKitten.Database>.self) { container in
    return try MongoKitten.Database.connect(settings: ConnectionSettings("mongodb://localhost/my-db"), on: container.eventLoop)
}

// Globally, add this so that the above code can register MongoKitten to Vapor's Services
extension Future: Service where T == MongoKitten.Database {}

// An adaptation of your function
func bulkAddCountries(req: Request, countries:[Country]) throws ->  Future<Response> {
    // Get a handle to MongoDB
    let database = req.make(Future<MongoKitten.Database>.self)

    // Make a `Document` for each Country
    let documents = try countries.map { country in
        return try BSONEncoder().encode(country)
    }

    // Insert the countries to the "countries" MongoDB collection
    return database["countries"].insert(documents: documents).map { success in
        return // Return a successful response
    }
}

I had a similar need and want to share my solution for bulk processing in Vapor 3. I’d love to have another experienced developer help refine my solution.

I’m going to try my best to explain what I did. And I’m probably wrong.

First, nothing special in the router. Here, I’m handling a POST to items/batch for a JSON array of Items.

router.post("items", "batch", use: itemsController.handleBatch)

Then the controller’s handler.

func createBatch(_ req: Request) throws -> Future<HTTPStatus> {
    // Decode request to [Item]
    return try req.content.decode([Item].self)
        // flatMap will collapse Future<Future<HTTPStatus>> to [Future<HTTPStatus>]
        .flatMap(to: HTTPStatus.self) { items in
        // Handle each item as 'itm'. Transforming itm to Future<HTTPStatus>
        return items.map { itm -> Future<HTTPStatus> in
            // Process itm. Here, I save, producing a Future<Item> called savedItem
            let savedItem = itm.save(on: req)
            // transform the Future<Item> to Future<HTTPStatus>
            return savedItem.transform(to: HTTPStatus.ok)
        }
        // flatten() : “Flattens an array of futures into a future with an array of results”
        // e.g. [Future<HTTPStatus>] -> Future<[HTTPStatus]>
        .flatten(on: req)
        // transform() : Maps the current future to contain the new type. Errors are carried over, successful (expected) results are transformed into the given instance.
        // e.g. Future<[.ok]> -> Future<.ok>
        .transform(to: HTTPStatus.ok)
    }
}
Related