I have a mongoose (3.1) 'Thing' schema whose toJSON I can customize in the following manner...
Thing.options.toJSON = {};
Thing.options.toJSON.transform = function (doc, ret, options){
// do something to ret, depending on options
}
As noted in the code comment, I would like to change the JSON representation given the value of options. I would like to pass these options in an expressjs action, maybe...
app.get(..., function (req ,res){
Thing.find({}, function(err, things){
var myOptions = {...} // something application stateful
return response.send(things) // MAYBE ADD OPTIONS HERE?
});
});
How do I modify expressjs to allow me to supply options?
Thanks,
G
IMHO, the accepted answer (#VladStirbu's) is wrong because the options are being set at the schema level. It's changing the schema, so those options will be available in subsequent calls, even if you don't request so explicitly.
The options should be set inline, individually for that call:
Regular call using express:
app.get(..., function (req ,res){
Thing.find({}, function(err, things){
return response.send(things);
});
});
Call using express, but passing inline options to toJSON():
app.get(..., function (req ,res){
Thing.find({}, function(err, things){
let toJSONOptions; // may be undefined, it's fine
if ( /* whatever condition you decide */ ) {
// this keeps the schema's original options:
toJSONOptions = Object.assign({ }, Thing.schema.options.toJSON);
// request to use original transform function, if any:
toJSONOptions.transform = true;
// set your own options to be passed to toJSON():
toJSONOptions._options = {...}; // whatever you need here
}
return response.send( things.map(e => e.toJSON(toJSONOptions)) );
});
});
No problem if toJSONOptions = undefined, it would be like a regular call to toJSON(), which is what express does when stringifying.
If you're using findOne() or findById(), then just return:
return response.send( thing.toJSON(toJSONOptions) );
This is the Mongoose commit that made me think of this:
https://github.com/Automattic/mongoose/commit/1161f79effc074944693b1799b87bb0223103220
You could pass options in the route handler by passing them to the schema options:
app.get(..., function (req ,res){
Thing.find({}, function(err, things){
Thing.schema.options.toJSON.myOptions = {...} // something application stateful
return response.send(things) // MAYBE ADD OPTIONS HERE?
});
});
this way, the options will be available in the transform function as a property of the options object:
Thing.options.toJSON.transform = function (doc, ret, options){
console.log(options.myOptions); // prints the app specific data provided earlier
}
Related
I created a post-user-registration hook, in which i would like to save some information to user_metadata. However, I don't see the data being saved
/*
#param {object} user.user_metadata - user metadata
*/
module.exports = function (user, context, cb) {
// Perform any asynchronous actions, e.g. send notification to Slack.
user.user_metadata = {
"someinfo": "abcd"
}
cb();
};
Something like:
module.exports = function (user, context, cb) {
var response = {};
user.user_metadata.foo = 'bar';
response.user = user;
return cb(null, response);
};
worked fine for me.
For rules the docs say that you can't directly update the user_metadata. As described on the link you have to use the updateUserMetadata function after you set the new values. I am not sure if this applies to hooks too (probably not, since the auth0 object is not defined on hooks).
p.s. Keep in mind that hooks only run for Database Connections, as outlined in the docs. Is there a chance you used an account based on social login?
I have 3 different method responses in the API I'm working on currently set up like this:
app.use('/image', require('./routes/image/get'));
app.post('/image', require('./routes/image/post'));
app.put('/image', require('./routes/image/put'));
Is there a better way to do this?
You may use .route() on your app's Express object to reduce some of the redundancy in your route definitions.
app.route('/image')
.post(require('./routes/image/post'))
.put(require('./routes/image/put'));
There is also .all(), which will invoke your handler regardless of the request http method.
No use()
I've omitted .use(), mentioned above, because it is not available on Route objects -- it sets up application middleware. Routers are another layer of middleware (see this question for an explanation of the difference). If the intent is really to call .use(), and not .get(), then that line would have to stay, before the call to .route() (middleware registration order matters).
Reusing the same handler for different http methods
If one would prefer to reuse the same handler for a set of methods, in the following style:
app.route("/image").allOf(["post", "put"], function (req, res) {
//req.method can be used to alter handler behavior.
res.send("/image called with http method: " + req.method);
});
then, the desired functionality can be obtained by adding a new property to express.Route's prototype:
var express = require('express');
express.Route.prototype.allOf = function (methods /*, ... */) {
"use strict";
var i, varargs, methodFunc, route = this;
methods = (typeof methods === "string") ? [methods] : methods;
if (methods.length < 1) {
throw new Error("You must specify at least one method name.");
}
varargs = Array.prototype.slice.call(arguments, 1);
for (i = 0; i < methods.length; i++) {
methodFunc = route[methods[i]];
if (! (methodFunc instanceof Function)) {
throw new Error("Unrecognized method name: " +
methods[i]);
}
route = methodFunc.apply(route, varargs);
}
return route;
};
I am writing a hapi js plugin, and was wondering what's the difference between the two ways of exposing methods that other plugins can use.
Method 1:
server.method("doSomething",
function () {
// Something
});
Method 2:
server.app.doSomething = function () {
// Something
};
In the first approach, the function can later be called as server.doSomething(), while using the second approach as server.app.doSomething().
So why would I use one way instead of another?
Looking at the API docs it sounds like they intended server.methods to be used for functions and server.app to be used for app settings/configuration. My guess is you should stick with server.method if you want to expose server level methods to be used in your plugins.
server.methods
An object providing access to the server methods where each server
method name is an object property.
var Hapi = require('hapi');
var server = new Hapi.Server();
server.method('add', function (a, b, next) {
return next(null, a + b);
});
server.methods.add(1, 2, function (err, result) {
// result === 3
});
server.app
Provides a safe place to store server-specific run-time application
data without potential conflicts with the framework internals. The
data can be accessed whenever the server is accessible. Initialized
with an empty object.
var Hapi = require('hapi');
server = new Hapi.Server();
server.app.key = 'value';
var handler = function (request, reply) {
return reply(request.server.app.key);
};
Is there a nice way to prevent duplicate routes from being registered in express? I have a pretty large application with hundreds of routes across different files, and it gets difficult to know if I've already registered a certain route when I go to add a new one. For example, I'd like to throw an error when express gets to routes487.js:
File: routes1.js
var ctrl = require('../controllers/testctrl');
var auth = require('../libs/authentication');
module.exports = function (app) {
app.get('/hi', auth.getToken, ctrl.hi);
app.get('/there', auth.getToken, ctrl.there);
};
File: routes487.js
var ctrl = require('../controllers/testctrl487');
var auth = require('../libs/authentication');
module.exports = function (app) {
app.get('/hi', auth.getToken, ctrl.hi487);
};
You could try a custom solution by wrapping express methods with the validation. Consider the following modification to your express app:
// route-validation.js
module.exports = function (app) {
var existingRoutes = {}
, originalMethods = [];
// Returns true if the route is already registered.
function routeExists(verb, path) {
return existingRoutes[verb] &&
existingRoutes[verb].indexOf(path) > -1;
}
function registerRoute(verb, path) {
if (!existingRoutes[verb]) existingRoutes[verb] = [];
existingRoutes[verb].push(path);
}
// Return a new app method that will check repeated routes.
function validatedMethod(verb) {
return function() {
// If the route exists, app.VERB will throw.
if (routeExists(verb, arguments[0]) {
throw new Error("Can't register duplicate handler for path", arguments[0]);
}
// Otherwise, the route is saved and the original express method is called.
registerRoute(verb, arguments[0]);
originalMethods[verb].apply(app, arguments);
}
}
['get', 'post', 'put', 'delete', 'all'].forEach(function (verb) {
// Save original methods for internal use.
originalMethods[verb] = app[verb];
// Replace by our own route-validator methods.
app[verb] = validatedMethod(verb);
});
};
You just need to pass your app to this function after creation and duplicate route checking will be implemented. Note that you might need other "verbs" (OPTIONS, HEAD).
If you don't want to mess with express' methods (we don't know whether or how express itself or middleware modules will use them), you can use an intermediate layer (i.e., you actually wrap your app object instead of modifying its methods). I actually feel that would be a better solution, but I feel lazy to type it right now :)
I want to save the results from a query using itemFileReadStore into an array called boxes, but the return value is empty (presumably because fetch is run asynchronously).
The gotItems function builds the array as I want it to, but I can't return that back to myself for any use! I could build the rest of my functionality into the gotItems part, but that would make my code unpretty.
How do I return an array for general use in my JavaScript from the gotItems function?
function getContentFile() {
contentStore = new dojo.data.ItemFileReadStore({
url: '../config/content.json',
preventCache : true
});
var boxes = new Array();
contentStore.fetch({query: {partner : 'enabled'}, onItem: gotItems });
return boxes;
}
function gotItems(item ) {
boxes.push( contentStore.getValue(item,'title') );
console.log( boxes );
return boxes;
}
dojo.addOnLoad( function() {
boxes = getContentFile();
console.log(boxes);
fadeIn('header', 500, 0);
});
Welcome to the world of asynchronous operations.
You'll need to do it with the "continuation-style" programming. ItemFileReadStore's fetch operations is asynchronous -- as you already know by passing the gotItems continuation to it.
contentStore.fetch({query: {partner : 'enabled'}, onItem: gotItems }) will return immediately. Your boxes will be empty at that point (because JavaScript is single-threaded). gotItems is executed after data arrived and subsequent to the function passed to dojo.addOnLoad returning.
You have to put your handling code:
console.log(boxes);
fadeIn('header', 500, 0);
inside the continuation gotItems itself. For example, something like:
function gotItems(item ) {
var boxes = [];
dojo.forEach(item, function(box) {
boxes.push( contentStore.getValue(box,'title') );
});
console.log(boxes); // You probably need to store "boxes" somewhere instead of just logging it
fadeIn('header', 500, 0);
}
Also, the data passed to onItems is an array, so you need to iterate it.
You don't have access to the results when the function returns because as you guessed, the fetch operation executes asynchronously.
You can either put the code that uses the results in your gotItems() function (as answered by Stephen), or you can use Deferreds and Promises. IMHO, that's a better alternative since it lets you organize your code better (once you get used to the idioms of dealing with promises, the code reads more naturally) and it allows you to transparently execute both synchronous and asynchronous operations.
See these two Dojo tutorials on the subject.
In your case, a possible solution involving deferreds would read like:
function getContentFile() {
contentStore = new dojo.data.ItemFileReadStore({
url: '../config/content.json',
preventCache: true
});
var dfd = new dojo.Deferred();
var boxes = new Array();
contentStore.fetch({
query: { partner : 'enabled' },
onItem: function(item) {
boxes.push( contentStore.getValue(item,'title') );
},
onComplete: function() {
// resolve the promise and execute the function in then()
dfd.callback(boxes);
}
});
return dfd;
}
dojo.addOnLoad( function() {
getContentFile().then(function(boxes) {
console.log(boxes);
fadeIn('header', 500, 0);
});
});