Search code examples
node.jsexpressexpress-session

With node.js / express-session is it possible to save() just one property on the session?


Environment: Node.js, Express, express-session

Question: Is it possible to save just one property of a session instead of saving over the entire session?

Background: Normally express-session saves the session at the end of each http response, i.e. when the route is finished. However it's possible to save a session at any time with req.session.save(). This saves the entire session. Normally that's not a problem but I have a use case where that is a problem.

Explanation: In the code below I pass req to subfunction. This function alters a property on the session and then saves the session with req.session.save(). Unfortunately when I passed req into subfunction req.session.myProperty was set to true. When I save the session it stores true when in the main route that property has already been changed to false. In subfunction what I need to do is update req.session.someOtherProperty and then only save that single property. Is that possible?

Example:

function myRoute(req, res) {
  
    req.session.myProperty = true;

    subFunction(req);  // runs in the background while the route continues.

    req.session.myProperty = false;    

    // do more stuff and return

}

function async subFunction(req) {

    // uh oh, req.session.myProperty was passed in as true.
    // In the main route it's already been changed to false.

    let testURLResult = await testIfURLActive('www.example.com'); 

    req.session.testURLResult = testURLResult;
    req.session.save(); // I need it to save just req.session.testURLResult, not the entire session.

    // req.session.myProperty was just saved as true because that's what passed in.  When I save it overwrites the false value that was set in the main route.

    return;

}

Solution

  • OK, so I think I'm finally understanding that the problem is that you have an asycnhronous operation that can take a while to run and when it's done you want to save one particular property in the session so it persists for future requests.

    But, if you use the req.session object you have, update that one property and call req.session.save() on that, you will be saving a stale session object that may overwrite other properties that have legitimately been changed by other requests while this long running request was running.

    The two ways I can think of to save just your one new property are:

    1. Directly access the session store and update just that one property in the session store.

    2. Ask express-session to get you a new, updated req.session, modify your one property and then call req.session.save() on that newly updated session object.

    Since you don't show anything about what type of session store you're using, I'll focus on the second option here. You can do that by removing the current req.session and calling the session middleware again which will essentially fetch a new, fresh copy of the session.

    You don't show your session initialization code, but let's just take an example from the doc of this:

    app.use(session({
      secret: 'keyboard cat',
      resave: false,
      saveUninitialized: true
    }));
    

    So, first change that to this:

    const sessionMiddleware = session({
      secret: 'keyboard cat',
      resave: false,
      saveUninitialized: true
    });
    app.use(sessionMiddleware);
    

    We need to save a copy of the session middleware so we can invoke it manually later.

    Then, in your long running function, you can use that like this:

    // note I am passing res and next here now too
    async function subFunction(req, res, next) {
        let testURLResult = await testIfURLActive('www.example.com'); 
    
        // remove the current session object from req to we can obtain a fresh one
        delete req.session;
        // repopulate req.session
        sessionMiddleware(res, req, err => {
            if (err) {
                next(err);
                return;
            }
            // fresh req.session here
            req.session.testURLResult = testURLResult;
            req.session.save();        
        });
    }
    

    Note, there are other issues here that should also be addressed:

    1. You have no error handling for testIfURLActive() or for calling subFunction(). If either can ever reject, then you will have an unhandled rejection which will terminate your server in the latest version of node.js. You need to catch rejections there unless that can never reject.

    2. Because the modified subFunction() needs to be able to act as full-blown middleware, I am now passing req, res, next to it. You will need to modify where you call it to also do that.