I'm working on a REST API with express to keep track of climbs you've completed. A user has a one to many relation with Climbs, and climb has a one to many relationship with notes.
I've chosen to make notes a nested resource within the climb router, as a note can't exist without a climb. This leads to making multiple checks at each level that a) when trying to update/delete a note the climb belongs to the current user, and b) that the note belongs to the climb, as well as them existing.
const deleteNote = expressAsyncHandler(async (req, res) => {
const climb = await PrismaClient.climb.findUnique({
where: {
id: climbId,
},
});
if (climb.userId !== req.user.id) {
res.status(403);
throw new ApiException(
'You are not authorized to view notes for this climb',
403
);
}
const note = await PrismaClient.note.findUnique({
where: {
id: noteId,
},
});
if (!note) {
throw new ApiException('Note not found', 404);
}
if (note.climbId !== climb.id) {
throw new ApiException(
'You are not authorized to view notes for this climb',
403
);
}
await PrismaClient.note.delete({
where: {
id: noteId
},
});
return res.status(204).end();
});
Making these checks gives me more granular control over the HTTP response codes I send back to the client, but there's also another way to do it like so:
const note = await PrismaClient.note.findFirst({
where: {
id: parseInt(noteId),
climb: {
id: parseInt(climbId),
userId: req.user.id, // Ensure the climb belongs to the authenticated user
},
},
});
if (!note) {
res.status(404);
throw new ApiException('Note not found or unauthorized', 404);
}
This approach leverages joins on the database level, and is slightly more complicated query as opposed to many small ones. This way though, I lose the capability to respond with a 403/404/etc depending on the scenario.
My question is, which of the two is better, or is it on a case by case basis?