Execute a server-side function from MongoClient in node.js - node.js

I've created a set of functions on my MongoDB server that I'd like to be able to use from MongoClient in my nodejs scripts. All of the documentation I've read tells me how to do this, but from the shell only it seems.
How it would normally work in the shell:
mongo database my.script.js
mongo
> use database
> db.loadServerScripts()
> add("This is a string taken in by the add function I just loaded")
This is what I've tried/looked into (mind the CoffeeScript):
MongoClient = require('mongodb').MongoClient
MongoClient.connect 'mongodb://127.0.0.1:27017/database', (e, db) ->
console.log db.eval #Function, but not sure what to call with
console.log db.runCommand #undefined
console.log db.loadServerScripts #undefined
console.log db.load #undefined
console.log db.command #Function, but not sure what to call with
console.log db.add #this is one of my custom functions
Hopefully this is possible with MongoClient. It seems as though I could use eval() if I can manage to load the script, but that's proving to be the difficult part so far. Alternatively, I suppose I could minify my functions and run those through eval(), but I'd prefer not to do that.

You can use eval (it looks like you don't need to load any functions first, the load command is only to make the functions available from the shell):
db.eval('add("This is a string taken in by the add function I just loaded")', function(err, result) {
...
});
Or, if you want to be a bit more flexible in passing arguments:
db.eval('function(x) { return add(x); }', [ ARG ], function(err, result) { ... });
(it seems that you can't just use 'add' as first argument, you need to wrap it in an anonymous function first, but I might be missing something...)

Related

Using NPM `redis` package, .exists(...) always returns true. Why?

I have a little Node.js project with Redis that is in development. What I am currently seeing is that client.exists(anything) returns true, even when that key does not exist in the Redis store. Here is some code to demonstrate what I am doing.
const key = 'k'
const content = 'blah blah'
client.rpush(key, content)
I run the above code and I stop. Now, on the command line, I do the following:
> redis-cli
:6379> exists k
true
:6379> exists foo
false
Awesome! As expected. Now, I run the following code back in Node.js:
const key = 'k'
if(client.exists(key)) console.log('should print')
if(client.exists('foo')) console.log('should not print')
Unfortunately, the result I'm getting in console output is:
should print
should not print
Why is Redis in Node.js reporting that something exists when Redis on the CLI reports, as expected, that thing does not exist?
Because you're using the library wrong, as it's asynchronous.
If you read the docs, you'll find that you need to pass in a result callback (or promisify the library to use promises instead).
client.exists(key, (err, ok) => {
if (err) throw err;
console.log(ok);
});

Getting a value from a Node.js callback

I'm trying to get my head around callbacks in Node.JS and it's pretty foreign compared to what I'm used to.
I have the following example from another post on here:
var someOutput;
var myCallback = function(data) {
console.log('got data: '+data);
someOutput = data;
};
var usingItNow = function(callback) {
callback('get it?');
};
//now do something with someOutput outside of the functions
I have added the someOutput variable - I want to get the text 'get it?' (obviously I don't) into there so that I can concatenate it on to a string, but it's not working in any combination I've tried.
I must be missing something fundamental here!
Thank you for the help.
You should call the function:
usingItNow(myCallback);
But anyway it is not a good practice to use callback to set variable and consume later. It will work for you now, but later if callback will be async, it will fail.
Consider using Promise and use someOutput in the then function.
A good Promise package is Bluebird
If you want to do it like a pro, consider using Promise.coroutine
http://bluebirdjs.com/docs/api/promise.coroutine.html
Or async + await if you are using an updated version of Node.js
Look on http://node.green/#ES2017-features-async-functions to check what avilable on your node version
As commented by emil:
"call usingItNow(myCallback);"
doh!

how to use mongo db.eval through Mongoose

From my mongo shell I can run functions with db.eval like:
db.eval('return 7;');
And, but for a deprecation warning, the parameter function is run properly.
Now I want to call such a 'script' from node.js, through mongoose.
I've tried to just call db.eval with the stringified function code as parameter (as in here):
mongoose.connect(PATH_TO_A_MONGO_DATABASE);
mongoose.connection.db.eval('return 9;', function(err, result){
console.log(result);
//etc.
});
This silently ignores the callback. No error is thrown, and the callback function is never called.
When doing this, I checked that mongoose.connection.db.eval is actually a function.
Since db.eval also has a Promise interface, also tried:
mongoose.connection.db.eval('return 5;').then(console.log).catch(console.log);
With the same result: no errors, just silence.
So, I guess I'm missing something with the syntax, but I can't find documentation or examples about this. Any similar questions like this or this didn't help.
PD1: I'm willing to do this because I need to call a procedure stored in system.js through mongoose. I can't do that too. However, I can't even run a silly script like return 5;, so I'm asking for the simpler task before. Any advice on how to call server scripts with mongoose is welcome.
PD2: I'm aware stored server scripts in mongo are a bad practice, and that they are deprecated for a reason and so on... but I can't just decide about that. I've been told to do that at my company, and the co-worker who set up the original code of the stored server script is not here now.
Ok, I figured out why my db.eval callbacks was being ignored. It is related with the mongoose connection.
If you start a connection like:
const conn = mongoose.connect(PATH_TO_DB);
And then just make a query:
conn.model(a_collection, a_schema).find({}).exec().then()...
Even if you just call the query right after the connection -it is, logically, an asynchronous process-, mongooose figures that it has to wait to the connection to be in a proper state, then fire the query, then call the callback with the query results.
However, this doesn't work the same way with db.eval(). just trying to call db.eval() right after the call to the connection, the callback is silently ignored:
const conn = mongoose.connect(PATH_TO_DB);
conn.db.eval('return 5;', (err, response) => {
//nothing happends
})
That was the way I was creating the connection and calling db.eval, so I couldn't get db.eval() to work.
In order to fire a db.eval and make it work, it seems that you need to wait for the connection explicitly:
const conn = mongoose.connect(PATH_TO_DB, err => {
mongoose.connection.db.eval('return 5', (err, result) => {
result; //5!
})
});
To make a query before any attemps to call db.eval also works:
const conn = mongoose.connect(PATH_TO_DB);
conn.model(a_collection, a_schema).find({}).exec()
.then(() => {
conn.db.eval('return 5', (err, result) => {
result; //5!
})
})

Set variable equal to mongodb key value

var userLat = db.collection('users', function (err, document){
document.findOne({_id: loggedUserID}, function(err, docs) {
console.log(docs.currentUserLat);
})
});
This is my code, I'm trying to get the value that's console logged into the variable. I just can't find the correct syntax to do this. The console log does return the correct value just need to drop it into the variable. Grateful for some help.
What do you want to do with 'docs.currentUserLat'?
You can do what you need to do without saving docs.currentUserLat to a variable that has scope outside of your db.collection call. Some examples:
If you simply want to change the document in your database, take advantage of the many methods specified in the Collections API: http://mongodb.github.io/node-mongodb-native/2.0/api/Collection.html. For example, to update the document and simultaneously resave it in the database:
db.collection('users', function (err, document){
document.findOneAndUpdate({_id: loggedUserID},
{currentUserLat: [updated value]},
function(err, docs) {
if(err) console.log(err);
}
)
});
If you just wanted to use docs.currentUserLat inside some node function, you'll need to properly nest the document.findOne function inside a callback (or vice versa). For example, to write currentUserLat to a file using the fs module:
var fs = require('fs');
db.collection('users', function (err, document){
document.findOne({_id: loggedUserID}, function(err, docs) {
fs.writeFile("pathToYourFile", docs.currentUserLat, function(err) {
if(err) {return console.log(err);}
});
});
});
Or, if you want to send it in response to a simple http request:
var http = require('http');
http.createServer(function(request,response){
db.collection('users', function (err, document){
document.findOne({_id: loggedUserID}, function(err, docs) {
response.writeHead(200,{'Content-Type':'text/html'});
response.end(docs.currentUserLat);
});
});
});
The key thing to remember is what JohnnyHK said in their comment: docs.currentUserLat is only available inside the anonymous function passed to findOne. So, whatever it is that you need to do, do it inside this function.
(Reading the link JohnnyHK provided is a great way to get started with understanding asynchronous functions in Node. Another is https://github.com/rvagg/learnyounode)
First of all you have to understand how javascript callback works. After that you will see that nothing assigns docs.currentUserLat to your userLat variable. The reason behind this is that your docs.currentUserLat is available only inside the callback. Think about it in the following way:
You program started to execute and encountered the line: var userLat = .... This line tells: do a callback (which basically asks someone else to do the job), your while your job is being executed the program continues, by assigning userLat to undefined and executes further. Then at some period of time callback finishes and console.log your docs.currentUserLat.
One way to have the desired behavior is to make userLat global and instead of console.log(docs.currentUserLat); do userLat = docs.currentUserLat. The problem that if you will do this, your userLat eventually will have the desired value (if callback will not fail), but you can not predict when. So if you will do
var userLat = db.collection('users', function (err, document){ ... });
.. some other code
console.log(userLat);
you will not be sure that you will get the output. Another way to do put everything in another callback.

Profiling mongoose methods

I want to see logs of calling methods for all my mongoose methods, like this:
# Load Book
LoadBook = (id, cb) ->
console.log 'loading book...'
Book.findById id, (err, book) ->
if err
console.log err
throw err
console.log 'loaded book: ' + book.title
cb book
I guess I can define post and pre methods like this:
BookSchema.pre 'save', (next) ->
console.log 'loading ' + `model_name(don't know how to get it)` + ' ...'
next()
And the same for other methods like findById or remove but it's long. And the error handling works only if I don't use callbacks, but I use it every time. I mean:
Part.on 'error', (err) ->
console.log "Got an error", err
I think it doesn't work when there is a callback, does it?
Perhaps there is some universal profiler in nodejs? I'm using express by the way.
You can enable debug logging in Mongoose by calling:
mongoose.set('debug', true);
With that enabled you'll get a log entry for every MongoDB operation made via Mongoose. Not sure if it's exactly what you want, but it's worth giving it a try.
You can use look module to profile your node.js app. It based on nodetime but works on local server.

Resources