Fastify plugin dependency - Registering MySQL before routes - node.js

I'm new to NodeJS and Fastify and I'm fairly sure I'm making a basic mistake here but I can't see what it is.
I have separated out my MySQL connection and routes into separate plugins. It appears that the MySQL plugin is not registering in time, leading to the routes not working.
I was under the impression that registering plugins was done asynchronously (loading one plugin at a time), but it can't seem to find "fastify.mysql.query".
Error:
"Cannot read properties of undefined (reading 'query')","stack":"TypeError: Cannot read properties of undefined (reading 'query')\n at Object.<anonymous> (/Users/dally/Projects/NodeJS/boodil-payments-api/routes/payments.js:4:23)\n at preHandlerCallback (/Users/dally/Projects/NodeJS/boodil-payments-api/node_modules/fastify/lib/handleRequest.js:126:28)\n at preValidationCallback (/Users/dally/Projects/NodeJS/boodil-payments-api/node_modules/fastify/lib/handleRequest.js:110:5)\n at handler (/Users/dally/Projects/NodeJS/boodil-payments-api/node_modules/fastify/lib/handleRequest.js:74:7)\n at handleRequest (/Users/dally/Projects/NodeJS/boodil-payments-api/node_modules/fastify/lib/handleRequest.js:22:5)\n at runPreParsing (/Users/dally/Projects/NodeJS/boodil-payments-api/node_modules/fastify/lib/route.js:487:5)\n at Object.routeHandler [as handler] (/Users/dally/Projects/NodeJS/boodil-payments-api/node_modules/fastify/lib/route.js:434:7)\n at Router.lookup (/Users/dally/Projects/NodeJS/boodil-payments-api/node_modules/find-my-way/index.js:368:14)\n at Server.emit (node:events:527:28)\n at parserOnIncoming (node:_http_server:956:12)"},"msg":"Cannot read properties of undefined (reading 'query')"}
Server.js
const fastify = require('fastify')({ logger: true })
fastify.register(require('./config/db'))
fastify.register(require('./routes/payments'))
const PORT = 2000
const start = async () => {
try {
await fastify.listen({ port: PORT })
}
catch (e) {
fastify.log.error(e)
process.exit(1)
}
}
start()
DB.js
module.exports = function (fastify, options, done) {
fastify.register(require('#fastify/mysql'), {
connectionString: 'mysql://root:password#localhost/boodil'
})
done()
}
payment.js
function paymentRoutes(fastify, opts, done) {
fastify.get('/get-transactions', (req, reply) => {
fastify.mysql.query(
'SELECT * FROM transactions',
function onResult(err, result) {
reply.send(err || result)
}
)
})
fastify.get('/get-transaction:id', (req, reply) => {
fastify.mysql.query(
'SELECT * FROM transactions where id = ?', [req.params.id],
function onResult(err, result) {
reply.send(err || result)
}
)
})
done()
}
module.exports = paymentRoutes

As said in comments, you need to use the fastify-plugin wrapper in your DB.js file.
To understand this error, you must get confident with fastify's encapsulation context.
It is worth reading these answers:
https://stackoverflow.com/a/61054534/3309466
https://stackoverflow.com/a/73586266/3309466
In practice: your database connection was isolated into a context that was unreachable by the paymentRoutes.
I'm coming from a PHP/Laravel background where this is quite standard
What fastify refuses is creating a global context where all you need is shared. Of course, you can do it, by it is not the default behaviour. You need to declare that the exported plugin has something that can be used by other contexts.

Related

How do I get around Internal Server Error when testing Azure Function

Does this Nodejs code look right? Is there anything missing?
const mysql = require('mysql');
const fs = require('fs');
var config =
{
host: 'mydemoserver.mysql.database.azure.com',
user: 'myadmin#mydemoserver',
password: 'your_password',
database: 'quickstartdb',
port: 3306,
ssl: {ca: fs.readFileSync("your_path_to_ca_cert_file_BaltimoreCyberTrustRoot.crt.pem")}
};
const conn = new mysql.createConnection(config);
conn.connect(
function (err) {
if (err) {
console.log("!!! Cannot connect !!! Error:");
throw err;
}
else {
console.log("Connection established.");
readData();
}
});
function readData(){
conn.query('SELECT * FROM inventory',
function (err, results, fields) {
if (err) throw err;
else console.log('Selected ' + results.length + ' row(s).');
for (i = 0; i < results.length; i++) {
console.log('Row: ' + JSON.stringify(results[i]));
}
console.log('Done.');
})
conn.end(
function (err) {
if (err) throw err;
else console.log('Closing connection.')
});
};
This is to go inside an Azure Function that reads data from a Azure for MySQL database.
When I run it inside the Kudu window by just typing node index.js it works
When I try and test it on there Azure Function Test Page it throws a Internal Server Error 500 with the following error message
Unable to determine function entry point. If multiple functions are exported, you must indicate the entry point, either by naming it 'run' or 'index', or by naming it explicitly via the 'entryPoint' metadata property.' Stack: Error: Worker was unable to load function ListBrands: 'Unable to determine function entry point. If multiple functions are exported, you must indicate the entry point, either by naming it 'run' or 'index', or by naming it explicitly via the 'entryPoint' metadata property.' at C:\Program Files (x86)\SiteExtensions\Functions\4.14.0\workers\node\dist\src\worker-bundle.js:2:13853 at t.LegacyFunctionLoader. (C:\Program Files (x86)\SiteExtensions\Functions\4.14.0\workers\node\dist\src\worker-bundle.js:2:14092) at Generator.next () at o (C:\Program Files (x86)\SiteExtensions\Functions\4.14.0\workers\node\dist\src\worker-bundle.js:2:12538) at processTicksAndRejections (node:internal/process/task_queues:96:5)
Thanks in advance
Todd

I get an error when I have 2 entries with similar name

When I have two similar posts with the same title I get this error.
I get the error when I access the page with the same name, having other entries with different names work just fine.
Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
at ServerResponse.setHeader (_http_outgoing.js:470:11)
at ServerResponse.header (D:\DD\documents\100dc\nodejs\gamesplayed\node_modules\express\lib\response.js:767:10)
at ServerResponse.send (D:\DD\documents\100dc\nodejs\gamesplayed\node_modules\express\lib\response.js:170:12)
at done (D:\DD\documents\100dc\nodejs\gamesplayed\node_modules\express\lib\response.js:1004:10)
at tryHandleCache (D:\DD\documents\100dc\nodejs\gamesplayed\node_modules\ejs\lib\ejs.js:257:5)
at View.exports.renderFile [as engine] (D:\DD\documents\100dc\nodejs\gamesplayed\node_modules\ejs\lib\ejs.js:482:10)
at View.render (D:\DD\documents\100dc\nodejs\gamesplayed\node_modules\express\lib\view.js:135:8)
at tryRender (D:\DD\documents\100dc\nodejs\gamesplayed\node_modules\express\lib\application.js:640:10)
at Function.render (D:\DD\documents\100dc\nodejs\gamesplayed\node_modules\express\lib\application.js:592:3)
at ServerResponse.render (D:\DD\documents\100dc\nodejs\gamesplayed\node_modules\express\lib\response.js:1008:7)
at games.forEach.game (D:\DD\documents\100dc\nodejs\gamesplayed\app.js:80:21)
at Array.forEach (<anonymous>)
at Game.find (D:\DD\documents\100dc\nodejs\gamesplayed\app.js:77:15)
at D:\DD\documents\100dc\nodejs\gamesplayed\node_modules\mongoose\lib\model.js:4759:16
at D:\DD\documents\100dc\nodejs\gamesplayed\node_modules\mongoose\lib\query.js:4099:12
at process.nextTick (D:\DD\documents\100dc\nodejs\gamesplayed\node_modules\mongoose\lib\helpers\query\completeMany.js:35:39)
app.post('/newentry', (req, res)=> {
const gameName = req.body.gameName;
const gameImage = req.body.gameImage;
const gamedesc = req.body.gamedesc;
const gameEntry = new Game({title: gameName, description: gamedesc, image: gameImage});
gameEntry.save();
res.redirect('/');
});
app.get('/:title', (req, res) => {
const gameTitle = _.lowerCase(req.params.title);
Game.find((err, games)=> {
games.forEach(game => {
const storedGame = _.lowerCase(game.title);
if (storedGame === gameTitle) {
res.render('post', {title: game.title, image: game.image, desc: game.description, id: game._id })
}
});
});
});
It happens because storedGame === gameTitle becomes true multiple times through the forEach loop. You can not call res.render multiple times during the same request.
Since it looks like you are using mongoose, I would suggest using Game.find to actually find what you are looking for, instead of pulling everything from the database and handling the searching yourself.
Games.find({title: gameTitle}, (err, games) => {
// Return what you want to the client.
})
This will still cause multiple games to be returned, so you need to handle that in some way before responding to the client.
Also look into Promises, as they make the code look much cleaner.
This error occurs when we try to send response more than one time.
const storedGame = _.lowerCase(game.title) will have multiple values and hence after comparing it will be there in the res.render block more than one time so this error will occur.
So comparing should be done in the query itself.
As you have used find, it will return all the games.So, You can try something like this.
Game.find((err, games)=> {
games= games.map(game => {
const storedGame = _.lowerCase(game.title);
if (storedGame === gameTitle) {
return {title: game.title, image: game.image, desc: game.description, id: game._id }
}
});
return res.send(games)
});
Or another way is to use findOne method of ORM to find only one record.

Sourcemaps in puppeteer?

I'm puppeteering (could this be a verb?) an app built with webpack and on page.on('error') I get errors like this:
TypeError: __WEBPACK_IMPORTED_MODULE_0_babel_runtime_core_js_promise___default.a.map is not a function
at _default.<anonymous> (....../app.js:23858:89)
at Generator.next (<anonymous>)
at step (....../app.js:35041:30)
at ....../app.js:35052:13
Is there a way, and if so how could I get these errors correctly sourcemapped?
(I know for sure I'm generating the sourcemaps)
I did it in the following way... A bit delayed but someone might find it useful.
Once you have navigated to the page throwing the error you inject this lib: https://github.com/novocaine/sourcemapped-stacktrace
await page.addScriptTag({
url: 'https://cdn.jsdelivr.net/npm/sourcemapped-stacktrace#1.1.8/dist/sourcemapped-stacktrace.js',
});
Then you listen for the error events:
page.on('pageerror', logStackTrace);
page.on('error', logStackTrace);
In the logStackTrace function you extract the sourcemapped stacktrace as follows and log it:
const logStackTrace = async (error) => {
page.evaluate(stack => new Promise(resolve =>
window.sourceMappedStackTrace.mapStackTrace(stack, (newStack) => {
resolve(newStack);
})
), typeof error.stack === 'string' ? error.stack : error.stack.join('\n'))
.then((result) => {
console.log('ERROR:', error.message, result[0]);
});
};
It only correctly maps the first line of the stack for me though. Still infinitely more useful.
If you use one of the eval variants for your webpack devtool then Puppeteer should pick it up:
// webpack.config.js
module.exports = {
// ...
mode: 'development',
devtool: 'eval-source-map',
};

Why? Error: Can't set headers after they are sent

I keep getting this error: Error: Can't set headers after they are sent.
I have read other posts but I don't understand. I dont have any double callbacks or anything. Where in my code is causing this error?
Error: Can't set headers after they are sent.
at ServerResponse.OutgoingMessage.setHeader (http.js:689:11)
at ServerResponse.header (/root/node_modules/express/lib/response.js:666:10)
at ServerResponse.send (/root/node_modules/express/lib/response.js:146:12)
at fn (/root/node_modules/express/lib/response.js:900:10)
at View.exports.renderFile [as engine] (/root/node_modules/jade/lib/jade.js:330:12)
at View.render (/root/node_modules/express/lib/view.js:93:8)
at EventEmitter.app.render (/root/node_modules/express/lib/application.js:530:10)
at ServerResponse.res.render (/root/node_modules/express/lib/response.js:904:7)
at Query. (/root/tutsplus/server4.js:25:7)
at Query.emit (events.js:98:17)
var express = require('express'), app = express();
var mysql = require('mysql');
app.get('/', function(req,res) {
var connection = mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'xxxx',
database: 'store'
});
var query = connection.query('SELECT * from category');
query.on('result', function(row) {
var category = row.category_name;
res.render('xxxx.jade', {
category: category
});
});
}); // app.get
app.listen(80, function() {
console.log('we are logged in');
});
As I said in my comment, this issue is nearly always caused by improper handling of asynchronous operations which causes pieces of the response to be called out of order.
Per the code example here that uses .on(), you need to end the request only when you get:
query.on('end', function() {
// all rows have been received
});
I think you are probably calling res.render() more than once because you're calling it in query.on('result', ...) rather than in query.on('end', ....) after all the data has been collected.
The fact that you're doing it in:
query.on('result', ...)
is probably the wrong timing issue causing the problem.
From the mysql nodejs connector documentation, here's an example:
var query = connection.query('SELECT * FROM posts');
query
.on('error', function(err) {
// Handle error, an 'end' event will be emitted after this as well
})
.on('fields', function(fields) {
// the field packets for the rows to follow
})
.on('result', function(row) {
// Pausing the connnection is useful if your processing involves I/O
connection.pause();
processRow(row, function() {
connection.resume();
});
})
.on('end', function() {
// all rows have been received
});

Error using Redis Multi with nodejs

I am using Redis and consulting it from nodejs, using the module Redis.
When i exec a client.multi() and the redis server is down the callback doesn't send the error and the nodejs app terminates.
This is the error
/Users/a/db/node_modules/redis/index.js:151
throw callback_err;
^
TypeError: Cannot read property 'length' of undefined
at Command.callback (/Users/a/db/node_modules/redis/index.js:1098:35)
at RedisClient.flush_and_error (/Users/a/db/node_modules/redis/index.js:148:29)
at RedisClient.on_error (/Users/a/db/node_modules/redis/index.js:184:10)
at Socket.<anonymous> (/Users/a/db/node_modules/redis/index.js:95:14)
at Socket.EventEmitter.emit (events.js:95:17)
at net.js:441:14
at process._tickCallback (node.js:415:13)
this is my code:
Constructor class
var redis = require('redis');
var client;
function Redis(){
client = redis.createClient();
client.on("error", function (err) {
console.log("Error " + err);
});
}
Redis.prototype.multi = function(commands,callback){
var err = null;
client.multi(commands).exec(function (error, res) {
if(error){
process.nextTick(function(){
callback(error,null)
})
}else{
process.nextTick(function(){
callback(null,res)
})
}
});
}
FYI, I ran across this in an old lib that depended on old version of node_redis.
This issue was a bug and was fixed in v0.9.1 - November 23, 2013: https://github.com/mranney/node_redis/pull/457
I think that people are still reaching here... (not sure if this answers this specific question directly, but I assume people reaching here since the multi.exec() returns true / the event loop is not waiting for it's response.
After the fixes that went in (in node-redis), it is possible to wrap the result of exec with Promise, and then you will be sure that the result will include the replies from the multi.
So, you can add some redis commands to the multi:
await multi.exists(key);
await multi.sadd(key2,member);
And then in the result do something like:
return new Promise((resolve, reject) => {
multi.exec((err, replies) => {
if (err) {
reject(err);
}
return resolve(replies);
});
});
Otherwise, if you will just do: const reply = await multi.exec();
it will just return you true, and not the replies
** Important to mention - this refers to 'async-redis' and 'node-redis'

Resources