I'm trying to make a transaction with mongoose.
When I'm running the code, it looks like mongoose document remembers the session even after calling await session.endSession()
. And later, when I call .save() on that document, I get error "Use of expired sessions is not permitted".
This is the demo (just a simplified example):
const MessageSchema = new mongoose.Schema({ text: String }, {strict: 'throw'});
const Message = mongoose.model('Message', MessageSchema);
const session = await mongoose.startSession();
let message;
await session.withTransaction(async () => {
message = (await Message.create([{text: 'My message'}], {session}))[0];
});
await session.endSession();
message.text = 'My message 2';
await message.save();
That final .save()
is throwing that error. If you would log message.$session().hasEnded
you would get true. My expectation was that, if session has ended, .save() would be smart not to use it.
What I want to achieve: Create some documents with transaction, commit them, so they are in the database.
Later, use that same document, that is already in the database, to make some changes to it.
What am I doing wrong here? How can I prevent .save()
from throwing an error and trying to use expired session?
There are no sections on endSession
anywhere in the mongoose docs, but as you very well discovered it yourself, when this function is called, it only sets a flag to your session
object rather than destroying it. If it was destroyed, you would have another error.
My expectation was that, if session has ended, .save() would be smart not to use it.
It is, in fact, smart enough not to use it, the framework only informs you that you are attempting an illegal instruction.
What I want to achieve: Create some documents with transaction, commit them, so they are in the database.
That's exactly what you do with your withTransaction
call. This wrapper helps you create, commit and abort/retry in case something bad happened, so after that call and if all ended well you indeed successfully created a document in the database.
Later, use that same document, that is already in the database, to make some changes to it.
Depends on what you mean by "later". If it's part of the same endpoint (and I don't know why you would immediately modify that document rather than committing it rightfully in the first place), then as I said in the comments, moving the endSession
call would likely fix the issue:
const MessageSchema = new mongoose.Schema({ text: String }, {strict: 'throw'});
const Message = mongoose.model('Message', MessageSchema);
const session = await mongoose.startSession();
let message;
await session.withTransaction(async () => {
message = (await Message.create([{text: 'My message'}], {session}))[0];
});
message.text = 'My message 2';
await message.save();
await session.endSession();
If it's part of another endpoint, then just make another transaction like you just did, except you modify the document instead of creating it. Or if you don't need transaction at all, use a method like findOneAndUpdate
or findByIdAndUpdate
. I reckon you seem to be familiar enough with JS and Mongoose to do that on your own.
What am I doing wrong here?
Basically, not much. You already understood that you can't call .save()
after ending a session.
How can I prevent
.save()
from throwing an error and trying to use expired session?
You can't. It is merely a reminder that you are attempting an illegal operation. You can however try/catch the error and decide to do nothing about it in your catch
clause, OR write a if statement checking for message.$session().hasEnded
.