Seeding a Development Database

A seed script has three jobs: refuse to run against the wrong database, be repeatable, and finish fast. The first is the one people skip — check the database name before the first write, because a copied MONGO_URI is the usual way production data disappears. Repeatability comes from fixed _id values: give every seed document a literal ObjectId in seed/data.js, upsert instead of dropping collections, and a rerun leaves the ids in your open URLs valid.

seed/seed.js — guard, upsert, then sync indexesJavaScript
const dbName = new URL(uri.replace('mongodb', 'http')).pathname.slice(1);
if (process.env.NODE_ENV === 'production' || !/_dev$|_test$/.test(dbName)) {
  console.error(`refusing to seed "${dbName}": name must end in _dev or _test`);
  process.exit(1);
}
const upsert = (doc) =>
  ({ updateOne: { filter: { _id: doc._id }, update: { $set: doc }, upsert: true } });
await Review.deleteMany({});                       // derived data: rebuild it
await Author.bulkWrite(authors.map(upsert));
await Book.bulkWrite(books.map(upsert));
await User.bulkWrite(users.map(({ password, ...u }) =>
  upsert({ ...u, passwordHash: bcrypt.hashSync(password, 10) })));
for (const M of [Author, Book, Review, User]) await M.syncIndexes();
Output
$ MONGO_URI=mongodb://127.0.0.1:28113/bookshelf_dev node seed/seed.js
seeded bookshelf_dev in 274 ms
  authors: 3   books: 5   reviews: 0   users: 2

A second run reports 190 ms and identical counts; pointing it at bookshelf exits 1. syncIndexes() belongs here rather than in the application: it creates the indexes the schemas declare and drops the ones they do not, which is wrong for production and right for a developer machine (Mongoose Transactions). Hash the fixture passwords at seed time — a bcrypt hash in source control is a user nobody can log in as.