I'm creating a JS web app using NodeJS and Express (with SQL/MySQL for the database), pretty much directly implementing this API tutorial: https://www.bezkoder.com/node-js-rest-api-express-mysql/ (just replacing 'tutorials' with 'Employees').
I'm trying to write API functions to get all Employees with certain attributes (in the SQL table), for example all employees with lastName = "Garcia" or all employees with teamID = 43682, etc.
In my routes.js file I have this:
module.exports = app => {
const employees = require("../controllers/employee.controller.js");
const router = require("express").Router();
// Create a new Employee
router.post("/", employees.create);
// Retrieve all Employees
router.get("/", employees.findAll);
// Retrieve all Employees with lastName
router.get('/', employees.findLastName);
... a bunch more CRUD functions ...
app.use('/api/employees', router);
};
And this is the corresponding Controller function:
exports.findLastName = (req, res) => {
const lastName = req.query.lastName; // tried changing req.query.lastName to req.params.lastName
Employee.getLastName(lastName, (err, data) => {
if (err)
res.status(500).send({
message:
err.message || "Error occurred while retrieving by last name."
});
else {
console.log(`Employees with lastName ${lastName} were found!` );
res.send(data);
}
});
};
exports.findAll = (req, res) => {
const title = req.query.title;
Employee.getAll(title, (err, data) => {
if (err)
res.status(500).send({
message:
err.message || "Some error occurred while retrieving employees."
});
else {
console.log(`Employee with ${title} title was found!` );
res.send(data);
}
});
};
The findAll route/function (just copied from that tutorial) works by finding all Employees with a certain ID number (the primary key in the DB) and I know that works from testing it through Postman. I wrote the findLastName route/function by copying the findAll function and changing it to search by lastName, and making the corresponding functions in the model and controller classes.
The new function, findLastName, doesn't work... unless I put the route before the findAll route (or comment it out). Then it correctly calls all my functions and returns all employees with the lastName param.
What's actually happening here? Are you not allowed to have multiple .get() routes or something? If so, how would I implement a search API like this? This is my first time building a web app like this so admittedly I'm still a little hazy on how routing and all that works. Thank you for any help though!
In Express whenever the first route matches second will be ignored, so in your scenario you have two route.get with same path /
router.get('/', employees.findAll);
//Since route with path `/` is matched above already this will be ignored
router.get('/', properties.findLastName);
In order to find Employees with last name you will need to create a new route with param (param will contain the last name)
router.get('/:lastName', properties.findLastName);
You can access the param value like this req.params.lastName in controller
I have an ordinary
var express = require('express')
Node express www page, using session, pug, etc as usual. My db calls
var db = require('./scripts/myHappyMysqlScript')
I'm naturally using mysql, so in the db script
var mysql = require('mysql')
So for example
app.get('/catPhotos', (req, response) => {
response.render('catPhotos.pug');
})
Say a page has a table showing something from the petNames database,
app.get('/pets', function(req, res, next) {
db.allPetNames(function(err, petsList) {
res.render('pets.pug',
{
'petsList': petsList,
'pretty' : true
})
})
all good so far.
But here's a case with three tables on the pug page, and three different database calls:
db.cats(function(err, c) {
db.dogs(function(err, d) {
db.budgies(function(err, b) {
res.render('bigScreen.pug',
{
'cats' : c,
'k9s': d,
'budgies': b,
'pretty' : true
})
})
})
})
I just nest them like that.
This does seem to work perfectly.
It correctly waits sequentially. Errors fall through and are handled properly, and so on.
But is there a better syntax, better way?
What's the Node Way for realâ„¢ Node, not-Swift, programmers?!
Perhaps given that I'm using the mysql library, if that's relevant.
Note, one better way overall is to use something like Ajax to just stream in each "part" of the web page. Indeed I do that all the time. What I'm asking here, assuming at res.render I indeed want to return all that info at once, is there something better than nesting like that? Cheers
You can get rid of nested database calls by using promises.
Since you mentioned that you are using mysql library for interacting with the database, unfortunately, this library doesn't provide a promise-based API. So to get rid of nested database calls in your code, you need to create a promise-based wrapper around the callback version of database calls.
For a general overview of what promises are and how they work, see the following links:
MDN - Promise.
MDN - Using Promises
Following is an example of how you can create a promise-based wrapper and then use that wrapper to get rid of nested database calls.
This promise-based wrapper is just a function that returns a promise. It creates a promise instance, wraps the underlying database call, and eventually when the database call returns the data, it notifies your code.
function getCats() {
return new Promise((resolve, reject) => {
// make the database call
db.cats((error, cats) => {
// in case of an error, reject the promise by
// calling "reject" function
// Also pass the "error" object to the "reject" function
// as an argument to get access to the error message
// in the code that calls this "getCats" function
if (error) {
reject(error);
return;
}
// if there was no error, call the "resolve" function
// to resolve the promise. Promise will be resolved
// in case of a successful database call
// Also pass the data to the "resolve" function
// to access this data in the code that calls this
// "getCats" function
resolve(cats);
});
});
}
Now in your route handler function, instead of calling db.cats(...), call this getCats wrapper function.
There are two ways you can call the function that returns a promise:
Promise-chaining (For details, visit the links mentioned above)
async-await syntax (Recommended)
The following code example uses async-await syntax. For this, first, mark the route handler function as async by using the async keyword before the function keyword. By doing this, we can use the await keyword inside this route handler function.
app.get('/pets', async function(req, res, next) {
try {
const cats = await getCats();
// similar wrappers for other database calls
const dogs = await getDogs();
const budgies = await getBudgies();
// render the pub template, passing in the data
// fetched from the database
...
catch (error) {
// catch block will be invoked if the promise returned by
// the promise-based wrapper function is rejected
// handle the error appropriately
}
});
The above code example only shows how to wrap the db.cats(...) database call in a promise-based wrapper and use that wrapper to get the data from the database. Similarly, you can create wrappers for db.dogs(...) and db.budgies(...) calls.
Instead of creating a separate promise-based wrapper for each database call, ideally, you should create a re-usable promise-based wrapper function that takes in a function to call and wraps that function call in a promise just like shown in the above code example, i.e. getCats function.
Parallel Database calls
One important thing to note in the above code is the route handler function
const cats = await getCats();
const dogs = await getDogs();
const budgies = await getBudgies();
is that this will lead to sequential database calls which may or may not be what you want.
If these database calls do not depend on each other, then you can call the promise-based wrappers in parallel using Promise.all() method.
The following code example shows how you can call your promise-based wrapper functions in parallel using Promise.all().
app.get('/pets', async function(req, res, next) {
try {
// "petsData" will be an array that will contain all the data from
// three database calls.
const petsData = await Promise.all([getCats(), getDogs(), getBudgies()]);
// render the pub template, passing in the data
// fetched from the database
...
catch (error) {
...
}
});
I hope this is enough to help you get rid of the nested database calls in your current code and start using promises in your code.
If you're trying to use MySQL with Nodejs, the module you should be looking for is mysql2 rather than mysql.
mysql2 provides a promise based approach and is a much refined version of mysql module for nodejs.
For example, for executing a query,
in mysql
con.query(sql_query, (err, rows, field)=>{ //some code here }
in mysql2, you can use the async approach as well as promise approach. Also, prepared statements in mysql2 are more easier than mysql.
//async approach
class A {
static async fn(sql, params){
const [data] = await con.execute(sql, [params]);
return data;
}
}
//promise approach remains same as **mysql** itself.
Here's the documentation for
mysql2 & more docs
If your database calls returned promises instead of using callbacks, you could:
const cats = await db.cats();
const dogs = await db.dogs();
const budgies = await db.budgies();
res.render('bigScreen.pug', {
cats : cats,
k9s: dogs,
budgies: budgies,
pretty : true
});
// Or request them all in parallel instead of waiting for each to finish
const [
cats,
dogs,
budgies
] = Promise.all([
dg.cats(),
dg.dogs(),
db.budgies()
]);
Simply convert the mysql functions into promises using the nodejs standard lib util.promisify
example:
const { promisify } = require('util');
const catsPromise = promisify(db.cats);
const dogsPromise = promisify(db.dogs);
const budgiesPromise = promisify(db.budgies);
async function routeHandler() {
let err = null;
try {
const cats = await catsPromise();
const dogs = await dogsPromise();
const budgies = await budgiesPromise();
} catch(error) {
err = error;
}
if (err) {
console.log(err);
// you should res.end() or res.render(someErrorPage) here
// failure to do so will leave the request open
} else {
res.render('bigScreen.pug', {
'cats' : cats,
'k9s': dogs,
'budgies': budgies,
'pretty' : true
});
}
}
Promise.all() method seems a more famous and cleaner way to make multiple calls in parallel like your use case.
But there is one more alternate way. : Multiple statement queries
To use this feature you have to enable it for your connection:
var connection = mysql.createConnection({multipleStatements: true});
Once enabled, you can execute multiple statement queries like any other query:
db.query('SELECT cats; SELECT dogs', function (error, results, fields) {
if (error) throw error;
// `results` is an array with one element for every statement in the query:
console.log(results[0]); // [{cat1,cat2}]
console.log(results[1]); // [{dog1,dog2}]
});
It is technically more efficient as requires less back and forth with MySQL connection.
(However, this feature is disabled by default as it allows for SQL injection attacks if values are not properly escaped). To use this feature you have to enable it for your connection.)
I am using knex 0.19.4 in node js 10.x. I have 2 SQL statements - Insert and Update which has to happen as a transaction.
// Using Var, so that below code has access to this variable
var sqlStageInsert = kx('stage').insert({
officeid: 'OFF000',
taskid: 'T002',
});
var sqlTaskPcUpdate = kx('task')
.update({ pc: 100})
.where('task.taskno', taskno)
.limit(1);
1st Try - 2nd SQL sqlTaskPcUpdate Not getting Executed
const sqlUpdateInsert = kx.transaction(function (trx) {
sqlStageInsert.transacting(trx)
.then(function () {
console.log(sqlTaskPcUpdate.toString()); // This is outputing correct SQL
return sqlTaskPcUpdate.transacting(trx);
})
.then(trx.commit)
.catch(trx.rollback);
});
await sqlUpdateInsert;
2nd Try - Getting error Transaction query already complete. This is based on Commit/rollback a knex transaction using async/await
await kx.transaction(async (trx) => {
try {
await sqlStageInsert.transacting(trx);
await sqlTaskPcUpdate.transacting(trx);
trx.commit();
} catch (error) {
trx.rollback();
throw error;
}
});
I would suggest you to try inserting the data in stage table first and then retrieve a common value which belongs to both the table for applying in where class of updating task table(Assuming both the table contain any one common column having same data).
Please note that as per knexjs.org website, knex.transaction() uses returning statement with respect to PostgreSQL to insert/ update more than one table to maintain consistency, however MySQL won't support transaction due to which I'm using return values in below code.
FYR, http://knexjs.org/#Transactions
Please refer below code snippet for your reference :
db.transaction(trx => {
trx.insert({
officeid: 'OFF000',
taskid: 'T002'
})
.into('stage')
.then(() => {
return trx('stage').where({taskid}).then(resp => resp[0].taskno)
})
.then(stageTaskno => {
return trx('task')
.update({pc: 100})
.where({stageTaskno})
.limit(1)
.then(resp => {
resp.json(resp)
})
})
.then(trx.commit)
.catch(trx.rollback)
});
hope this is helpful, cheers!
There is a field called counter in a model and whenever if I call a custom remote method like
Request URL
http://loopback:3000/api/models/increment_counter
Request body
EMPTY
Response
{
"counter" : [Value after Increment]
}
Currently to increment First i have to get the counter value from db and increment it one and update the couter value, This involves two queries, Is it possible to do it in a single NodeAPI call like the below mysql query.I am currently using Mysql as DB.
mysql_query("
UPDATE model
SET counter = counter + 1
WHERE model_id = '1'
");
Thank you
Given the MySQL syntax you want, you seem to need an atomic counter.
database transactions
With the MySQL connector, you can use database transactions. It is supported by the MySQL connector. It's a bit overkill for just atomic counters but it will get the job done.
Here is an example, inside a custom remote method
MyModel.someRemoteMethodForIncrementing = function(callback) {
// Start the transaction
MyModel.beginTransaction({
isolationLevel: MyModel.Transaction.READ_COMMITTED
}, function(err, tx) {
// Retrieve the current counter value as part of the transaction
MyModel.findById(id, {
transaction: tx
}, function(err, data) {
if (err) {
console.log(err);
return tx.rollback(function(err) {
callback(err);
});
}
// Increment the counter as part of the transaction
var inc = data.counter + 1;
MyModel.updateAttributes({
counter: inc
}, {
transaction: tx
}, function(err, newData) {
if (err) {
console.log(err);
return tx.rollback(function(err) {
callback(err);
});
}
// Commit the transaction to make it happen
tx.commit(function(err) {
if (err) return callback(err);
// Counter should have been incremented
callback();
});
});
});
});
};
I haven't tested it but it should work, let me know
extented operators
In the future, you will be able to use the $inc (increment) extended operator but so far it's only supported by MongoDB connector, not MySQL.
Just for reference, here is the syntax (works only with MongoDB)
PUT api/Model/:id?filter={"$inc":{"name":propertyToIncrement, "count":incrementAmount}}
There is an ongoing PR that I am trying to get landed to get MySQL support, but there are many things to be done before it can get merged.
Yes you can do it in a single loopback remote method call. Assume you are sending an Id in your request
yourModel.remoteMethod = function(data, cb){
yourModel.findById(data.id, function(err, object){
if(err){
cb(err, null);
}
object.counter += 1;
object.save(function(saveErr, afterSaveObj){
cb(saveErr, afterSaveObj.counter);
})
});
}
here cb is a callback which loopback passes to your remoteMethod.
So I'm selecting Activities from the mongodb and populating User for each.
var query = Activity.find(query).populate("user");
return query.sort({created:"desc"}).exec(function(err, activities) {
debugger;
if (!err) {
return res.json(activities);
} else {
res.status(400).json(err);
}
});
As you can see I have a debugger; breakpoint is there, When I'm pring activities it prints an array of activities with the user object populated.
Also when I'm calling something like activities[0].toJSON() I get everything good!
But the response comes back with the user property empty !
I looked into the source of express.response.json(OBJ) and saw this line:
var body = JSON.stringify(val, replacer, spaces);
val is my activities
When calling JSON.stringify(activities) it will create a json with an empty user field.. any suggestions ?
Try the lean option. That gives back plain JS objects with no mongoose weirdness. Also, your error handling seems a little awkward, can be simplified.
var query = Activity.find(query).populate("user");
query.sort({created:"desc"}).lean().exec(function(err, activities) {
if (err) return res.status(400).json(err);
res.json(activities);
});
I would go even further, not hard-coding error sending in routes but simply passing along via if (err) return next(err) to error-handling middleware defined elsewhere in your app. You can still set the status, then use detection in your middleware, something like this:
app.use(function(err, req, res, next){
err.status = err.status || 500;
res.status(err.status).json(err);
});