Start here. This is the direct spoken answer to practice first.
Why this question matters
A pre-check improves the user message but cannot serialize concurrent requests. The database constraint is the final arbiter that prevents two valid-looking attempts from committing the same business identity.
I encode the real uniqueness rule as a unique constraint or unique index. Two requests can both run SELECT and see no duplicate, so an application-only check loses the race; the database allows one insert and rejects the other. The API catches the specific constraint violation and maps it to a stable conflict response rather than returning a generic 500. I may still check earlier for a friendly message, but correctness does not depend on that check.