Performing multiple database actions within functions can be complex, especially when handling failures. That's why I utilize a transaction session from Mongoose
to easily revert any actions if needed.
To begin, I initialize a session using the startSession
function and incorporate it into various Model.create
functions. Finally, I commit and end the session at the function's conclusion.
Given that I use an asyncHandler wrapper across all my functions, I want to streamline the process of introducing the session into the asyncHandler or another wrapper to manage transaction rollback in case of errors during these functions.
Example of Register Function
import { startSession } from 'mongoose';
import Company from '../models/Company';
import Person from '../models/Person';
import User from '../models/User';
import Mandate from '../models/Mandate';
import asyncHandler from '../middleware/asyncHandler';
export const register = asyncHandler(async (req, res, next) => {
const session = await startSession();
let entity;
if(req.body.profile_type === 'company') {
entity = await Company.create([{ ...req.body }], { session });
} else {
entity = await Person.create([{ ...req.body }], { session });
}
// Create user
const user = await User.create([{
entity,
...req.body
}], { session });
// Create mandate
await Mandate.create([{
entity,
status: 'unsigned'
}], { session });
// Generate user account verification token
const verification_token = user.generateVerificationToken();
// Send verification mail
await sendAccountVerificationMail(user.email, user.first_name, user.language, verification_token);
await session.commitTransaction();
session.endSession();
res.json({
message: 'User succesfully registered. Check your mailbox to verify your account and continue the onboarding.',
})
});
asyncHandler Helper Function
const asyncHandler = fn => ( req, res, next) => Promise.resolve(fn(req, res, next)).catch(next);
export default asyncHandler;
EDIT 1
To clarify, I'm seeking a solution (be it through one or more wrapper functions or an alternative method) to eliminate the repetition of lines containing // ~ repetitive code
. This includes managing try/catch blocks and the initialization and termination of a database transaction.
export const register = async (req, res, next) => {
const session = await startSession(); // ~ repetitive code
session.startTransaction(); // ~ repetitive code
try { // ~ repetitive code
let entity;
if(req.body.profile_type === 'company') {
entity = await Company.create([{ ...req.body }], { session });
} else {
entity = await Person.create([{ ...req.body }], { session });
}
const mandate = await Mandate.create([{ entity, status: 'unsigned' }], { session });
const user = await User.create([{ entity, ...req.body }], { session });
const verification_token = user.generateVerificationToken();
await sendAccountVerificationMail(user.email, user.first_name, user.language, verification_token);
await session.commitTransaction(); // ~ repetitive
session.endSession(); // ~ repetitive
res.json({
message: 'User succesfully registered. Check your mailbox to verify your account and continue the onboarding.',
});
} catch(error) { // ~ repetitive
session.abortTransaction(); // ~ repetitive
next(error) // ~ repetitive
} // ~ repetitive
};