I'm starting with sequelize to create an API and I'm facing an issue with transactions.
My sequelize database configuration looks like this:
var Sequelize = require('sequelize');
var sequelize = new Sequelize(CONFIG.database, env.user,
env.password, {
host: env.host,
dialect: env.dialect,
port: env.port,
operatorsAliases: false
});
var db = {};
fs.readdirSync(__dirname).filter(function (file) {
return (file.indexOf('.') !== 0) && (file !== 'index.js');
}).forEach(function (file) {
var model = sequelize.import(path.join(__dirname, file));
db[model.name] = model;
});
Object.keys(db).forEach(function (modelName) {
if ('associate' in db[modelName]) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;
Then I have a stockcontroller with functions to save in database like this:
var exports = module.exports = {}
let Stock = require('../models').Stock;
let StockVariant = require('../models').StockVariant;
exports.create = function (req, res) {
const body = req.body;
console.log(body);
Stock.create(body).then(function (stock, created) {})...}
I want to create transaction to save into stockvariant and stock tables in one single transaction and have the option to rollback when error.
Documentation in sequelize doesn't look easy for me to understand as I don't see how to apply this
return sequelize.transaction(function (t) { return User.create({}) })
because t is of course not defined anywhere and my stockcontroller doesn't import sequelize.
So in the end I don't understand the basic concept of how to define that transaction function to create a new stock line.
Thanks for your help!
The sequelize instance needs to be imported to use transactions. It is already exported in your database configuration file with this line db.sequelize = sequelize.
All you need to do is adding it in the current imports :
var exports = module.exports = {}
const Stock = require('../models').Stock; // Prefer const usage to avoid overwritting imports
const StockVariant = require('../models').StockVariant;
const sequelize = require('../models').sequelize;
This could also be done in one line using destructuring :
const { Stock, StockVariant, sequelize } = require('../models');
Now let's come to the transaction. As stated in the documentation, you have two ways of handling them : managed or unmanaged.
Managed transaction
This is done by chaining your asynchronous operations inside the sequelize transaction callback. In this case, if the operations linked to the transaction succeed, the transaction will commit automatically, otherwise it will rollback.
exports.create = function (req, res) {
const body = req.body;
console.log(body);
sequelize.transaction(function(t) {
return Stock.create(body, {transaction: t}) // We pass the transaction as a parameter here !
.then(function(stock, created) {
return StockVariant.create(..., {transaction: t}) // The transaction, again here
})
.catch(function(err) {
// Handle your error...
});
}
Unmanaged transaction
If you want more transparency and/or control over your transaction, you can use an unmanaged transaction. In this case, you must call commit and rollback manually.
exports.create = function (req, res) {
const body = req.body;
console.log(body);
sequelize.transaction
.then(function(t) { // Note the 'then' usage here
return Stock.create(body, {transaction: t}); // We pass the transaction as a parameter here !
.then(function(stock, created) {
return StockVariant.create(..., {transaction: t}); // The transaction, again here
});
.then(function() {
return t.commit();
})
.catch(function(err) {
return t.rollback();
});
}
This could also be done with async / await syntax, which may be more pleasant to read :
exports.create = function (req, res) {
const body = req.body;
console.log(body);
let t; // The variable in which the transaction object will be stored
try {
t = await sequelize.transaction();
const stock = await Stock.create(body, {transaction: t})
await StockVariant.create(..., {transaction: t}) // Whatever parameter you need to pass here
await t.commit();
} catch (err) {
await t.rollback();
}
}
Related
I'm new to Node, and I'm trying to follow a pattern from a Udemy API course. The API is structured to utilize route, controller and service modules for flow. Database queries are to be run as services and they are supposed to be called from controllers.
I need to run a series of DB queries to generate a list (I'm showing only 2 of 6 queries in this example). I am running these using async/await in my function. The queries are working fine. My problem occurs when I try to return the 'batch result' (the result of all the queries) to the controller at the end of the process. I get Promise { <pending> }. I have tried many things, but I cannot end the promise to access the final result from my controller module--I can only access it from my service module.
Here is my code from my controller module (groups.controller.js) where I call my function:
const groupsService = require('../services/groups.service');
exports.propertyList = (req, res, next) => {
const uid = req.body.uid;
const batchResponse = groupsService.batchQuery(uid, res);
console.log(batchResponse);
}
And here is my code from my service module (groups.services.js) where I run the queries:
const mysql = require('mysql2');
const dbAsync = require("../config/db.config");
async function batchQuery(uid, res) {
var Q1;
var Q2;
var uid = uid * -1;
const pool = mysql.createPool(dbAsync.dbAsync);
const promisePool = pool.promise();
try {
Q1 = await promisePool.query('SELECT PropertyID FROM GroupMembership WHERE GroupID = ?', [uid]);
Q2 = await promisePool.query('SELECT SubGroupID FROM GroupMembership WHERE GroupID = ? AND PropertyID = ?', [uid, 0]);
}
catch(error) {
console.log(error);
res.status(401).send('Server error');
return error;
}
finally {
const batchResponse = {
Q1: Q1[0],
Q2: Q2[0]
}
console.log('Q1: '+ Q1[0][0].PropertyID + ', Q2: ' + Q2[0][0].SubGroupID);
res.status(200).send(batchResponse);
return batchResponse;
}
}
module.exports = {batchQuery};
When I send a post via postman, I get the expected query result (below). However, I can only get this to work if I put my res in my service module.
{
"Q1": [
{
"PropertyID": 0
}
],
"Q2": [
{
"SubGroupID": 397
}
]
}
Is there a way to end the promise in this pattern and return the desired batch response? Thank you.
EDIT: Adding the code updates provided by #traynor.
New controller:
const groupsService = require('../services/groups.service');
exports.propertyList = async (req, res, next) => {
const uid = req.body.uid;
let batchResponse;
try {
batchResponse = await groupsService.batchQuery(uid);
console.log(batchResponse);
return res.status(200).send(batchResponse);
} catch(error) {
console.log('Error: ' + error);
return res.status(401).send('Server error');
}
}
New service:
const mysql = require('mysql2');
const dbAsync = require("../config/db.config");
function batchQuery(uid) {
return new Promise((resolve, reject) => {
var Q1;
var Q2;
var uid = uid * -1;
const pool = mysql.createPool(dbAsync.dbAsync);
const promisePool = pool.promise();
try {
Q1 = await promisePool.query('SELECT PropertyID FROM GroupMembership WHERE GroupID = ?', [uid]);
Q2 = await promisePool.query('SELECT SubGroupID FROM GroupMembership WHERE GroupID = ? AND PropertyID = ?', [uid, 0]);
} catch(error) {
console.log(error);
reject(error);
} finally {
const batchResponse = {
Q1: Q1[0],
Q2: Q2[0]
}
console.log('Q1: '+ Q1[0][0].PropertyID + ', Q2: ' + Q2[0][0].SubGroupID);
resolve(batchResponse);
}
})
}
module.exports = {batchQuery};
the service is now returning a promise, and it's also handling response instead of controller.
to return from service, you need to promisify service: return a promise which resolves when you get db data, or on error, and then you also need to await the service, which it's wrapped in try/catch for error handling.
once it's all done, handle response from the controller:
service:
function batchQuery(uid) {
return new Promise(async (resolve, reject) => {
var Q1;
var Q2;
//...
try {
//...
} catch (error) {
console.log(error);
reject(error);
} finally {
const batchResponse = {
Q1: Q1[0],
Q2: Q2[0]
}
console.log('Q1: ' + Q1[0][0].PropertyID + ', Q2: ' + Q2[0][0].SubGroupID);
resolve(batchResponse);
}
});
controller:
exports.propertyList = async (req, res, next) => {
const uid = req.body.uid;
let batchResponse;
try {
batchResponse = await groupsService.batchQuery(uid);
console.log(batchResponse);
res.status(200).send(batchResponse);
} catch(error) {
return res.status(401).send('Server error');
}
}
I'm quite new to Node JS, and I'm trying to build an API based on MySQL.
In one of my routers I'm trying to inject an insert query and based on it, get the new generated task id from mysql.
The problem is that the second query is not waiting for the response and sometimes I'm getting an error because taskId variable is undefined because it still didn't get the results from the first query.
the problematic variable that is not getting it's value correctly is taskId.
I'm attaching my code for your review, thanks for your help!
As requested: I'm attaching my required moudle as well:
const dotenv = require('dotenv');
const mysql = require('mysql');
dotenv.config();
var connection = mysql.createPool({
host: process.env.DB_HOST,
user: process.env.DB_USERNAME,
password: process.env.DB_PASSWORD,
database: process.env.DB_TABLE,
port: process.env.DB_PORT
});
module.exports = connection;
router.post('/new', auth, async (req, res) => {
const uid = req.body.uid;
const taskName = req.body.taskName;
const description = req.body.description;
const createdDate = req.body.createdDate;
const estimatedDate = req.body.estimatedDate;
const status = req.body.status;
let taskId = '';
addTaskQuery = `INSERT INTO task (title,description,status) VALUES ('${taskName}','${description}','${status}')`;
findTaskIdQuery = `SELECT id FROM task WHERE title = '${taskName}'`;
try {
// Injecting into task table
await connection.query(addTaskQuery, (err, results) => {
if(err) {
console.log(err);
return res.send(JSON.stringify({data: err}));
}
})
// Getting the new inserted task id
await connection.query(findTaskIdQuery, (err, results) => {
if(err) {
console.log(err);
return res.send(JSON.stringify({data: err}));
}
taskId = JSON.stringify(results[0].id);
})
// Injecting into havetask table
await connection.query(`INSERT INTO havetask (id,userId,taskId) VALUES (${taskId},${uid},${taskId})`, (err, results) => {
if(err) {
console.log(err);
return res.send(JSON.stringify({data: err}));
}
})
}
catch(err) {
console.log(err);
return res.status(401).json({ msg: 'An error occured while tried to add task'});
}
})
The mysql package you use does not support Promises (=== it doesn't do async / await). So your await statements don't wait, they just fall through.
You need to try a package that handles async / await. This one might do the trick.
I'm a new learner to express js and sequelizejs. I successfully migrate table in my database so the connection is fine I guess.
Here is my code.
https://github.com/Picks42/express-test
Please review this file
https://github.com/Picks42/express-test/blob/master/models/user.js
Then review this one
https://github.com/Picks42/express-test/blob/master/controller/test.js
Let me know what's the issue.
// all the models using your index.js loader
const models = require('../models');
// the user model, note the capital User since
const M_Bank = models.User;
exports.getTest = function(req,res){
return M_Bank
.findAll()
// don't use M_Bank here since you are getting an array of Instances of the Model
.then(users => res.status(200).send(users))
.catch((error) => {
console.log(error.toString());
res.status(400).send(error)
});
/* this will never execute because it is after the return
exports.index = function (request, response, next) {
response.json((M_Bank.findAll()));
};
*/
};
If you have the option of using async/await it makes for more readable code.
const models = require('../models');
const M_Bank = models.User;
exports.getTest = async function(req, res) {
try {
const users = await M_Bank.findAll();
return res.status(200).send(users);
} catch (err) {
console.log(err.toString());
return res.status(400).send(err);
}
};
You should get rid of the .User field in the 3rd line. because you've exported User itself from the models/user file.
Also, I recommend you not to mess with variables names. M_Bank variable doesn't speak itself
const M_Bank = require('../models/user');
exports.getTest = function(req,res){
return M_Bank
.findAll()
.then(M_Bank => res.status(200).send(M_Bank))
.catch((error) => {
console.log(error.toString());
res.status(400).send(error)
});
exports.index = function (request, response, next) {
response.json((M_Bank.findAll()));
};
};
Building a small MVC. When I'm receiving results back from my model, the variable that I'm using to send to my view is undefined if I use the "var" keyword. If I don't use the keyword the object comes through just fine. What is happening?
Controller
const homeModel = require('../models/homeModel.js');
exports.index = function(req, res){
homeModel.getAllStores(function (err, res) {
if (err) return err;
stores = res; // Works
var stores = res // Undefined
})
console.log(stores);
res.render('home', {stores: stores});
}
Here is the Model
const db = require('../db.js');
exports.getAllStores = function(done) {
db.query('select * from stores;', (err, rows) => {
if (err) return done(err);
let resultJson = JSON.stringify(rows);
resultJson = JSON.parse(resultJson);
return done(null, resultJson);
})
}
You need to move the declaration of stores to the function enclosing homeModel.getAllStores(). This is because JavaScript is function (lexically) scoped, so a variable will be scoped to the nearest enclosing function. You can read more about how variables that are declared using var work on MDN.
In Node.js, if you don't provide the var keyword before your variable then it is globally scoped to the module in which it is running, this is why console.log(stores) works when you use stores = res and not var stores = res.
To properly scope your variable using var, just move your declaration to the function being exported.
Additionally, your console.log() and res.render() calls are occurring before the callback function for homeModel.getAllStores() is executed and setting stores = res. Since res.render() and console.log() will only work as expected within the callback to homeModel.getAllStores() you can simplify index() and the callback to homeModel.getAllStores().
const homeModel = require('../models/homeModel.js')
exports.index = (req, res) => {
return homeModel.getAllStores((err, stores) => {
if (err) {
throw err
}
console.log(stores)
return res.render('home', {stores})
})
}
You could also use util.promisify() and async/await to write this a little more straightforward.
const {promisify} = require('util')
const getAllStores = promisify(require('../models/homeModel').getAllStores)
const index = async (req, res) => {
let stores
try {
stores = await getAllStores()
} catch (err) {
console.error(err)
return res.sendStatus(500)
}
return res.render('home', {stores})
}
module.exports = {index}
Here is an example with Promise.all() waiting for the results from multiple queries with a hypothetical UserModel with getAllUsers() that works identically to homeModel.getAllStores() but queries a users table.
const {promisify} = require('util')
const getAllUsers = promisify(require('../models/userModel').getAllUsers)
const getAllStores = promisify(require('../models/homeModel').getAllStores)
const index = async (req, res) => {
let queryResults
try {
queryResults = await Promise.all([getAllStores, getAllUsers])
} catch (err) {
console.error(err)
return res.sendStatus(500)
}
let [stores, users] = queryResults
return res.render('home', {stores, users})
}
module.exports = {index}
I have a form with one field that allows user to enter multiple developer id via comma delimited (ab1234,bc5678).
Once the form is submitted I perform the following tasks:
Get the the project
Loop through array of developer IDs to get their full name using mySQL
update the project using MongoDB
I'm new and sure this this is possible, The codes I have below is not working for me. Can someone please let me know if the codes below is even close.
const mongoose = require('mongoose'
const mysql = require('mysql');
// Create mySQL connection
const mySQLdb = mysql.createConnection({
host : 'localhost',
user : 'root',
password : 'root',
database : 'projects'
});
const Project = mongoose.model('project');
router.post('/developerSave', async (req, res) => {
let devList = req.body.dev_ids,
devIdArr = devList.split(','),
rData = {};
// get project
const project = await Project.findById(req.body.projectID);
mySQLdb.connect();
for(var i=0, len=devIdArr.length; i < len; i++) {
let sql = `SELECT CONCAT(first_name, ' ', last_name) as full_name FROM users WHERE id= '${devIdArr[i]}'`;
mySQLdb.query(sql, function (err, results) {
if (err) throw err;
let newDev = {
userId: devIdArr[i],
fullName: results[0].full_name
}
project.developers.unshift(newDev);
await project.save();
});
}
mySQLdb.end();
rData.success = true;
rData.msg = 'Developer was added successfully.';
res.status(200).json(rData);
});
The reason you are seeing this is because your await project.save(); is inside the callback function. Your main function will not wait for all the callbacks to complete and close the db connection. Lets look at the example below
const myCallback = (param, callback) => {
setTimeout(() => {
console.log('callback function', param);
callback();
}, 1000)
}
const myAsync = async () => {
console.log('inside async');
const result = await axios.get('http://google.com/');
for (let i = 0; i < 10; i++) {
myCallback(i, () => {
console.log('this is the actual callback function');
});
}
const result2 = await axios.get('http://bing.com/');
console.log('after second call');
}
myAsync();
The output of this is
inside async
after second call
callback function 0
this is the actual callback function
...
As you can see, the after second call is printed before the callback functions.
To solve this problem, you can wrap your callback function in a promise and resolve that once save is complete.