CodingNic

Sample Data & Wrap-Up

Write a Seed Script

Sample Data & Wrap-Up 25 min read

Write a Seed Script

Write a Seed Script

This script runs outside of Next.js entirely — it’s a plain Node script
you’ll invoke with npm run db:seed, the same way db/init.js applies
your schema.

Add the npm script

Open package.json and add a db:seed entry alongside db:init:

json
"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start",
  "db:init": "node db/init.js",
  "db:seed": "node db/seed.js"
}

Write the script

Create db/seed.js:

javascript
require("dotenv").config({ path: ".env.local" });
const { Pool } = require("pg");

const connectionString = process.env.DATABASE_URL;
if (!connectionString) {
  console.error("DATABASE_URL is not set. Copy .env.example to .env.local and fill it in first.");
  process.exit(1);
}

const keepExisting = process.argv.includes("--keep");

const GROUPS = ["Work", "Family", "Friends", "Book Club"];

const CONTACTS = [
  { name: "Ada Lovelace", phone: "555-0101", email: "ada@example.com", company: "Analytical Engines Ltd", notes: "Met at the algorithms meetup.", favorite: true, groups: ["Work", "Book Club"] },
  { name: "Grace Hopper", phone: "555-0102", email: "grace@example.com", company: "Navy Research", notes: "", favorite: true, groups: ["Work"] },
  { name: "Alan Turing", phone: "555-0103", email: "alan@example.com", company: "", notes: "", favorite: false, groups: ["Work", "Book Club"] },
  { name: "Priya Ramaswamy", phone: "555-0203", email: "priya.r@example.com", company: "Design Co-op", notes: "Runs the book club on Thursdays.", favorite: true, groups: ["Friends", "Book Club"] },
  { name: "Mom", phone: "555-0301", email: "mom@example.com", company: "", notes: "Call on Sundays.", favorite: true, groups: ["Family"] },
  // Add as many more as you like — this is just sample data.
];

async function main() {
  const pool = new Pool({ connectionString });
  const client = await pool.connect();
  try {
    await client.query("BEGIN");

    if (!keepExisting) {
      console.log("Clearing existing contacts and groups...");
      await client.query("DELETE FROM contact_groups");
      await client.query("DELETE FROM contacts");
      await client.query("DELETE FROM groups");
    }

    const groupIdByName = {};
    for (const name of GROUPS) {
      const result = await client.query(
        `INSERT INTO groups (name) VALUES ($1)
         ON CONFLICT (name) DO UPDATE SET name = EXCLUDED.name
         RETURNING id`,
        [name]
      );
      groupIdByName[name] = result.rows[0].id;
    }
    console.log(`Seeded ${GROUPS.length} groups.`);

    let count = 0;
    for (const c of CONTACTS) {
      const result = await client.query(
        `INSERT INTO contacts (name, phone, email, company, notes, favorite)
         VALUES ($1, $2, $3, $4, $5, $6) RETURNING id`,
        [c.name, c.phone || null, c.email || null, c.company || null, c.notes || null, !!c.favorite]
      );
      const contactId = result.rows[0].id;
      for (const groupName of c.groups) {
        const groupId = groupIdByName[groupName];
        if (!groupId) continue;
        await client.query(
          `INSERT INTO contact_groups (contact_id, group_id) VALUES ($1, $2)
           ON CONFLICT DO NOTHING`,
          [contactId, groupId]
        );
      }
      count++;
    }
    console.log(`Seeded ${count} contacts.`);

    await client.query("COMMIT");
    console.log("Done.");
  } catch (err) {
    await client.query("ROLLBACK");
    console.error("Seeding failed:", err.message);
    process.exitCode = 1;
  } finally {
    client.release();
    await pool.end();
  }
}

main();

A few design decisions worth noticing:

  • It clears before it seeds, by default. Without that, running the
    script twice would double every contact. Clearing first makes the
    script safe to re-run any time you want to reset to a known state —
    which is exactly what a seed script is for.
  • --keep is an escape hatch. process.argv.includes("--keep")
    checks for a command-line flag; running npm run db:seed -- --keep
    skips the clearing step, so you can layer sample data onto contacts
    you’ve already created by hand.
  • The whole thing is one transaction. If seeding fails partway
    through — a typo in a group name, say — ROLLBACK means you’re left
    with your previous data intact, not a half-seeded database.
  • It reuses the exact same group-linking pattern (ON CONFLICT
    upsert for groups, then insert into contact_groups) you already wrote
    by hand in Module 8’s setContactGroups. Seeing the same pattern show
    up in a second, independent piece of code is a good sign it was the
    right abstraction the first time.

Test it

bash
npm run db:seed

You should see Seeded 4 groups. and Seeded 5 contacts. in your
terminal. Reload the Home page — your hand-inserted test contacts are gone,
replaced by the seeded set, correctly grouped and favorited. Run it again:
the counts should stay exactly the same, confirming it’s safe to re-run.
Then try:

bash
npm run db:seed -- --keep

You should end up with 10 contacts instead of 5 — the seed data added on
top of what was already there.

Checkpoint

npm run db:seed reliably populates a realistic dataset with contacts and
groups, is safe to run repeatedly, and supports both “start fresh” and
“add on top” modes.