Search code examples
node.jstransactionsknex.js

How to I return query data after committing a transaction (using knex.js)?


I'm trying to register a user. The process is as follows - I first generate a hash for the user, then generate a token. Assuming these 2 steps are successful, I then add the user to the database.

This step involves adding the user to 2 tables - a user_credentials table containing just the hash, and using the id returned after this operation to add the other user details and token to a users table.

I am able to successfully add the user - however, when I try to access the user data returned by knex after adding the user to both tables, I always get Data: undefined.

Not sure what's the correct way to send the row data back from createUser(), and have it available in the next then block (where it's console logged).

const handleRegister = (req, res, db, bcrypt, saltRounds) => {
    // Get the details from the body
    let user = req.body;
    // Hash the plain text password using bcrypt
    bcrypt.hash(user.password, saltRounds)
        .then((hash) => {
            delete user.password;
            // set user's hash to encrypted pw
            user.hash = hash; 
        })
        // Create token to be sent back to the client to create a session
        .then(() => createToken())
        // Set user's token to created token
        .then((token)=> {
            user.token = token;
        })
        // Save hashed password to db for the user and save user data to db with created token
        .then(() => createUser(user, db))
        // Return info of created user
        .then((data) => {
            delete user.hash;
            console.log("User:", user);
            console.log("Data:", data);
            res.status(201).json(user);
        })
        .catch((err) => {
            console.log("This was the error:", err);
            res.status(400).json("Something went wrong");
        });
}


// creating a user in the database
const createUser = (user, db) => {
    return db.transaction((trx) => {
        trx.insert({
            hash: user.hash
        })
        .into('user_credentials')
        .returning('id')
        .then((userId) => {
            return trx('users')
                .returning('*')
                .insert({
                    user_id: userId[0],
                    email: user.email,
                    name: user.name,
                    token: user.token,
                    created_at: new Date()
                });
        })
        .then((data)=> {
            return trx.commit()
            .then(() => data);
        })
        .catch((err) => {
            trx.rollback();
            throw err;
        })
    })
}

Solution

  • OK, after sleeping on it, seem to have found the solution.

    It appears that db.transaction creates a pending promise, which is either resolved by trx.commit or rejected by trx.rollback. So the entire transaction with commit and rollback needed to be considered as one block, with a then block afterwards returning the data rows after a successful insert. See code below.

    References if someone wants to know more - https://github.com/tgriesser/knex/issues/1346 http://knexjs.org/#Transactions

    const createUser = (user, db) => {
        return db.transaction((trx) => {
            trx.insert({
                hash: user.hash
            })
            .into('user_credentials')
            .returning('id')
            .then((userId) => {
                return trx('users')
                    .returning('*')
                    .insert({
                        user_id: userId[0],
                        email: user.email,
                        name: user.name,
                        token: user.token,
                        created_at: new Date()
                    });
            })
            .then(trx.commit)
            .catch((err) => {
                trx.rollback();
                throw err;
            })
        })
    //Return the data here instead of along with trx.commit
        .then((data) => {
            return data;
        })
        .catch((err) => {
            console.log(err);
        });
    }