In an email-invitation voting app, how can the database itself guarantee that each invited email address is registered only once?
Put a unique index on the email field and simply attempt the insert — a second registration fails with duplicate-key error code 11000, which the app catches and answers with "already participates".
* The invitation-voting flow: a unique index gates registration; email plus token gate the vote. *
The scenario: an organizer registers voters' email addresses, and each invitee receives a personal link carrying their email plus a random secret token; voting later happens through that link. Registration stores one document per invitee:
const token = genHexString(24); // random hex secret for this invitee
try {
await votes.insertOne({ email: data.email, token: token, vote: null });
sendEmail(/* link with ?email=...&token=... */);
res.status(200).json({ message: data.email + ' added successfully' });
} catch (error) {
if (error.code == 11000) { // duplicate key: email already registered
res.status(400).json({ message: data.email + ' already participates' });
} else { throw error; }
}
The point of the pattern: don't check-then-insert — insert and let the unique index decide. A separate "does this email already exist?" query followed by an insert leaves a race window in which two simultaneous requests both pass the check; the unique index enforces the constraint atomically, so the second insert reliably throws. The duplicate-key error (code 11000) is therefore not a failure to hide but an expected signal the application turns into a meaningful response. vote: null marks an invitee who has not voted yet.
Go deeper:
Time-of-check to time-of-use (Wikipedia) — the general race-condition class that check-then-insert falls into, and why atomic constraint enforcement avoids it.