fullstackhero/dotnet-starter-kit · error · CustomException
A product with SKU ' ' already exists.
Error message
A product with SKU '{product.Sku}' already exists. What it means
CreateProductCommandHandler checks whether any Product already uses the same SKU before inserting. If taken, it throws CustomException with HttpStatusCode.Conflict, producing HTTP 409. SKU is a unique business key, so duplicates are rejected explicitly rather than by a DB unique constraint.
Solutions
- Check SKU availability first (list/search products by SKU) and use a different SKU.
- If this is a retry, GET the product by SKU — the original create likely succeeded; reuse the existing product instead of re-posting.
- Add client-side idempotency: disable the submit button while the request is in flight.
- If a unique constraint violation surfaces instead, it means the app-level check raced — catch 409/unique-violation and surface a friendly message.
Example fix
// before
await api.createProduct({ sku: form.sku, name: form.name });
// after
const existing = await api.searchProducts({ sku: form.sku });
if (existing.items.length > 0) {
throw new Error(`SKU ${form.sku} is already in use`);
}
await api.createProduct({ sku: form.sku, name: form.name }); Defensive patterns
Strategy: validation
Validate before calling
const dup = await api.searchProducts({ sku });
if (dup.items.length > 0) throw new Error(`SKU ${sku} already in use`); Try / catch
try {
await api.createProduct(payload);
} catch (e) {
if (e.status === 409) {
showError(`SKU ${payload.sku} already exists`);
return;
}
throw e;
} Prevention
- Debounce submit buttons and disable while the create request is in flight
- Pre-validate SKU uniqueness with a lookup before POST
- Use idempotency keys or check-then-fetch on retries of failed creates
When it happens
Trigger: POST /products (v1) whose derived Product.Sku equals the Sku of any existing product row — duplicate form submission, retry of a request that actually succeeded, or importing a catalog that already contains the SKU.
Common situations: Double-clicking 'Create' or replaying a POST after a timeout; CSV/bulk import colliding with existing inventory; two environments sharing one database.
Related errors
- A product with name ' ' already exists.
- Another product with name
- A brand with name ' ' already exists.
- Another brand with name
- Group with name ' ' already exists.
AI-assisted analysis of fullstackhero/dotnet-starter-kit@3f2959e683 (2026-09-15).
Data as JSON: /api/errors/3a0300c54001420d.
Report an issue: GitHub.
Appendix: source
Thrown at src/Modules/Catalog/Modules.Catalog/Features/v1/Products/CreateProduct/CreateProductCommandHandler.cs:49
{
throw new NotFoundException($"Category {command.CategoryId} not found.");
}
var product = Product.Create(
command.Sku,
command.Name,
command.Description,
command.BrandId,
command.CategoryId,
new Money(command.PriceAmount, command.PriceCurrency),
command.Stock);
bool skuTaken = await dbContext.Products
.AnyAsync(p => p.Sku == product.Sku, cancellationToken)
.ConfigureAwait(false);
if (skuTaken)
{
throw new CustomException(
$"A product with SKU '{product.Sku}' already exists.",
(IEnumerable<string>?)null,
HttpStatusCode.Conflict);
}
bool slugTaken = await dbContext.Products
.AnyAsync(p => p.Slug == product.Slug, cancellationToken)
.ConfigureAwait(false);
if (slugTaken)
{
throw new CustomException(
$"A product with name '{command.Name}' already exists.",
(IEnumerable<string>?)null,
HttpStatusCode.Conflict);
}
dbContext.Products.Add(product);
await dbContext.SaveChangesAsync(cancellationToken).ConfigureAwait(false);View on GitHub (pinned to 3f2959e683)