Skip to main content

insertMany

Another Lesan surprise: insertMany keeps all relationships embedded correctly while inserting many documents at once. It's fast โ€” changes go to the database as a single aggregation pipeline.

Insert many citiesโ€‹

const addCitiesValidator = () => {
return object({
set: object({
multiCities: array(object(countryCityPure)),
country: objectIdValidation,
}),
get: coreApp.schemas.selectStruct("city", 1),
});
};

const addCities: ActFn = async (body) => {
const { country, multiCities } = body.details.set;

return await cities.insertMany({
docs: multiCities,
projection: body.details.get,
relations: {
country: {
_ids: new ObjectId(country),
relatedRelations: {
cities: true,
citiesByPopulation: true,
capital: false,
},
},
},
});
};

coreApp.acts.setAct({
schema: "city",
actName: "addCities",
validator: addCitiesValidator(),
fn: addCities,
});

Two important points:

  1. Validation happens before any database command. multiCities is an array of pure city objects, and the whole payload โ€” including each city's fields โ€” is validated up front:

    "multiCities": [
    { "name": "Beirout", "abb": "BI", "population": 20000000 },
    { "name": "Baalbak", "abb": "BA", "population": 850000 }
    ]

    Wrong data is rejected before anything reaches Mongo.

  2. Single relations must be false. We explicitly set capital: false because we don't know which city will become the capital. In general, one-to-one relations in insertMany should always be false โ€” if they were true, the related relation would be updated once for every new document, which is usually wrong.

insertMany accepts the same shape as insertOne, but docs is an array:

  • docs โ€” an array of pure-field objects.
  • relations โ€” the relation input (same as insertOne).
  • projection โ€” shapes the written data.
  • options (optional) โ€” MongoDB BulkWriteOptions.

The full example is examples/document/10-1-insertMany.ts.

Before running addCities:

Cities before insertMany in Compass

While running:

Cities while insertMany runs

After running โ€” both cities are in the collection, embedded in the country's lists:

Cities after insertMany in Compass

Insert many users โ€” all relation types at onceโ€‹

The next example exercises almost every relation shape in one insertMany:

const addUsersValidator = () => {
return object({
set: object({
multiUsers: array(object()),
country: objectIdValidation,
livedCities: array(objectIdValidation),
lovedCity: objectIdValidation,
}),
get: coreApp.schemas.selectStruct("user", 1),
});
};

const addUsers: ActFn = async (body) => {
const { country, multiUsers, livedCities, lovedCity } = body.details.set;
const obIdLivedCities = livedCities.map((lp: string) => new ObjectId(lp));

return await users.insertMany({
docs: multiUsers,
projection: body.details.get,
relations: {
country: {
_ids: new ObjectId(country),
relatedRelations: {
users: true,
},
},
livedCities: {
_ids: obIdLivedCities,
relatedRelations: {
users: true,
},
},
mostLovedCity: {
_ids: new ObjectId(lovedCity),
relatedRelations: {
lovedByUser: true,
},
},
},
});
};

coreApp.acts.setAct({
schema: "user",
actName: "addUsers",
validator: addUsersValidator(),
fn: addUsers,
});

Here all the relation types appear together:

  • one-to-many โ€” country: the users store the country's pure fields; on the country side the users are stored in the users list.
  • many-to-many โ€” livedCities: an array of city ids; each user stores those cities' pure fields in livedCities, and each city stores the users in its users list.
  • one-to-many (single) โ€” mostLovedCity: a single city id stored in each user, while the city keeps the users in lovedByUser.

The full example is examples/document/10-2-insertMany.ts.

Before running addUsers:

Users before insertMany in Compass

While running:

Users while insertMany runs

After running โ€” all users inserted, with every relation embedded on both sides:

Users after insertMany in Compass

Insert many countriesโ€‹

Inserting countries needs no relations argument at all, since country has none โ€” the implementation is left to you. A real-world example lives in the benchmark.

The interesting takeaway: because relations are defined once and embedded automatically, the benchmark's insertMany implementation was trivial โ€” and the insert time is remarkably short.

Next stepsโ€‹