I'm building a guest creation API using Next.js (App Router), Zod, and Mongoose.
The problem:
Even though my frontend validation passes and I see the full payload in the console, the server throws validation errors and still creates the document. On the next request it then says the email already exists.
I am also using TanStack, and this API has a retry count of 2, which is why there are two response at the same time.
First error response:
{
"message": "Server error",
"error": "Guest validation failed: createdBy: Path `createdBy` is required., email: Path `email` is required., lastName: Path `lastName` is required., firstName: Path `firstName` is required., organizationId: Path `organizationId` is required."
}
Second request immediately shows:
{"message": "Guest with this email already exists"}
So somehow the first request fails validation AND still creates the guest.
My controller:
export async function createGuest(request) {
try {
const session = await getServerSession(authOptions);
const userId = session?.user?.id;
const organizationId = session?.user?.organizationId;
if (!userId || !organizationId) {
return NextResponse.json(
{ message: "Authentication required" },
{ status: 401 }
);
}
const body = await request.json();
const parsed = GuestCreateSchema.safeParse(body);
if (!parsed.success) {
return NextResponse.json(
{ message: "Validation failed", errors: parsed.error.flatten() },
{ status: 422 }
);
}
const { guest } = await createGuestService({
data: parsed.data,
organizationId,
createdByUserId: userId,
});
return NextResponse.json(
{ message: "Guest created successfully", guest },
{ status: 201 }
);
} catch (err) {
if (err.message === "Guest with this email already exists") {
return NextResponse.json({ message: err.message }, { status: 409 });
}
return NextResponse.json(
{ message: "Server error", error: String(err?.message || err) },
{ status: 500 }
);
}
}
Service:
export async function createGuestService({
data,
organizationId,
createdByUserId,
}) {
const existingGuest = await GuestRepository.findOne({
organizationId,
email: data.email.toLowerCase(),
});
if (existingGuest) {
throw new Error("Guest with this email already exists");
}
const guestPayload = {
organizationId,
salutation: data.salutation || null,
firstName: data.firstName,
lastName: data.lastName,
email: data.email.toLowerCase(),
phone: data.phone || null,
alternatePhone: data.alternatePhone || null,
address: data.address || {},
idType: data.idType || null,
idNumber: data.idNumber || null,
idIssuedCountry: data.idIssuedCountry || null,
dateOfBirth: data.dateOfBirth || null,
nationality: data.nationality || null,
preferences: data.preferences || {},
guestType: data.guestType || "individual",
companyName: data.companyName || null,
notes: data.notes || null,
tags: data.tags || [],
isVIP: data.isVIP || false,
vipNotes: data.vipNotes || null,
createdBy: createdByUserId,
};
const guest = await GuestRepository.create(guestPayload);
return { guest };
}
My question:
Why is Mongoose creating the document even though it throws validation errors such as "Path X is required"?
What causes this partial/failed validation write, and how can I prevent the document from being created when validation fails?
Any help is appreciated!
here the repository this handle's all the transcations
import Guest from "@/server/models/guestModel";
import Hotel from "@/server/models/hotel/hotelModel";
import User from "@/server/models/userModel";
const GuestRepository = {
// Create document
create(data, options = {}) {
return Guest.create(data, options);
},
// Find one document
findOne(query, projection = undefined, options = undefined) {
return Guest.findOne(query, projection, options);
},
// Find multiple documents
find(query, projection = null, options = undefined) {
return Guest.find(query, projection, options);
},
// Find by ID
findById(id, projection = null, options = undefined) {
return Guest.findById(id, projection, options);
},
// Find by ID and update
findByIdAndUpdate(id, update, options = {}) {
return Guest.findByIdAndUpdate(id, update, { new: true, ...options });
},
// Find one and update
findOneAndUpdate(query, update, options = {}) {
return Guest.findOneAndUpdate(query, update, { new: true, ...options });
},
// Count documents
countDocuments(query = {}) {
return Guest.countDocuments(query);
},
// Delete one
deleteOne(query) {
return Guest.deleteOne(query);
},
// Delete many
deleteMany(query) {
return Guest.deleteMany(query);
},
};
export default GuestRepository;
Guestdocument is created, but you're not showing that code anywhere. Please create a Minimal, Reproducible Example.