Examples include: Email addresses Usernames Phone numbers Product slugs Invitation codes
A common approach is to check whether the value already exists before saving it.
A uniqueness pre-check improves UX. A unique database constraint protects data integrity. You usually want both.
Before creating the account, the application can check whether the email already exists:
The pre-check improves the user experience because it explains the problem early.
Security note: Be careful with email availability messages. Saying “This email is already registered” can reveal whether someone has an account. In sensitive flows, use a more general message, such as: “This email cannot be used.” “If an account exists for this email, you will receive further instructions.”
The username was available when both requests checked it. But both requests checked it before either request saved it.
A unique database constraint makes the database responsible for enforcing the rule.
Now the database guarantees that two users cannot have the same username or email address.
Even if two requests arrive at the same time, only one can successfully create the record.
This protects the data even when the write comes from: A web form An API client A background job An admin tool A migration A webhook A script
The database constraint protects the data, but users should not see a raw database error.
The server should catch the unique-constraint error and return a friendly response.
The database constraint handles the situations that the pre-check cannot guarantee.
The user might submit a form and receive an error only after the request finishes.
It can fail because: Two requests can arrive at the same time. A different API may skip the check. A background job may write directly to the database. A bug may bypass the application validation. A malicious client may send requests directly.
| Layer | Purpose | | --- | --- | | Application pre-check | Gives users early and helpful feedback | | Database constraint | Guarantees that duplicate data cannot be stored | | Error handling | Converts conflicts into a friendly response |
If you use this approach, consider: Debouncing the request Checking only after a minimum number of characters Rate-limiting the endpoint Checking again when the form is submitted
