I need to set a ReST API with my NodeJS Express 4 Application.
Currently, this is my API.
I have a families resource which exposes several HTTP verb.
GET to perform a read in my MongoDB database.
GET with familyID to get the family with the id familyID
POST to create a new family in the database.
PUT to update a family.
I want to follow the ReSTful theory so I'd like to control when a PUT is done that all the resource is modified and not a part of it (which is a PATCH verb).
This my nodejs route controller code :
// Main Function
router.param('famillyId', function(req, res, next, famillyId) {
// typically we might sanity check that famillyId is of the right format
Familly.findById(famillyId, function(err, familly) {
if (err) return next(err);
if (!familly) {
errMessage = 'familly with id ' + famillyId + ' is not found.';
console.log(errMessage);
return next(res.status(404).json({
message: errMessage
}));
}
req.familly = familly;
next();
});
});
/PUT
router.put('/:famillyId', function(req, res, next) {
console.log('Update a familly %s (PUT with /:famillyId).', req.params.famillyId);
req.familly.surname = req.body.surname;
req.familly.firstname = req.body.firstname;
req.familly.email = req.body.email;
req.familly.children = req.body.children;
req.familly.save(function(err, familly) {
if (err) {
return next(err);
}
res.status(200).json(familly);
});
});
I'd like to know what is the best way to do this control. I don't want to use a series of 'if' for each record of my JSON object. Is there an automatic way of doing it ?
Just to avoid this kind of code :
if (req.familly.surname)
if (! req.body.surname)
return next(res.status(200).json('{"message":"surname is mandatory"}‘)));
Doing this kind of things for each property in my JSON Object is very boring, lots of code to type for nothing.
I looking forward a clean code to do it.
Thanks.
Hervé
var control = ['surname', 'firstname', 'email', 'children'];
control.forEach(function(arg){
if(!req.body[arg]){
return next(res.status(200).json({"message": arg + " is mandatory"}));
}
});
Related
As a personal project, I'm trying to build a social media site for teddy bear collectors. I would like users to be able to make a "collection" page which they can populate with individual profile pages for each of their bears. Finally, I would like other users to be able to comment on both the collection page and the individual profile pages.
However, I'm running into an error on the "/new" route for comments on the individual profile pages. I can't get it to find the id for the parent collection.
Below is the code I'm working with. I start by finding the id for the collection, then I get try to get the id for the bear (individual profile page). However, the process keeps getting caught at the first step.
var express = require("express");
var router = express.Router({mergeParams: true});
var Bear = require("../models/bear");
var Comment = require("../models/comment");
var Collection = require("../models/collection");
var middleware = require("../middleware");
//comments new
router.get("/new", middleware.isLoggedIn, function(req, res) {
Collection.findById(req.params.id, function(err, foundCollection) {
if(err || !foundCollection){
req.flash("error", "Collection not found");
return res.redirect("back");
}
Bear.findById(req.params.bear_id, function(err, foundBear) {
if(err){
res.redirect("back");
} else {
res.render("bcomments/new", {collection_id: req.params.id, bear: foundBear, collection: foundCollection});
}
});
});
});
//bcomments create
router.post("/", middleware.isLoggedIn, function(req, res){
Collection.findById(req.params.id, function(err, foundCollection) {
if(err || !foundCollection){
req.flash("error", "Collection not found");
return res.redirect("back");
}
//look up bear using id
Bear.findById(req.params.id, function(err, foundBear){
if(err){
console.log(err);
res.redirect("/bears" + bear._id);
} else {
//create new comment
Comment.create(req.body.comment, function(err, comment){
if(err){
req.flash("error", "Something went wrong");
console.log(err);
} else {
//add username and id to comment
comment.author.id = req.user._id;
comment.author.username = req.user.username;
//save comment
comment.save();
//connect new comment to bear
bear.comments.push(comment);
bear.save();
//redirect bear show page
req.flash("success", "Successfully added comment");
res.redirect("/collections/" + foundCollection._id + "/bears/" + foundBear._id);
}
});
}
});
});
});
So, instead of rendering the new comment form, it hits a "null" error and redirects back at the first if statement.
If anyone can help me figure this out, I'd be exceedingly grateful.
Thank you.
I think the problem is that you are defining the path "/new" (which has no parameters) and trying to access req.params.id. If you expect to have the parameter id you should define it in the path like this: router.get("/new/:id", .... Check the Express oficial documentation for more details.
EDIT:
You may have mixed req.params with req.query and req.body. If you are passing parameters in the request, you must access them through req.query (for example: req.query.id or req.query.bear_id) in the case of GET and DELETE or through req.body in POST and PUT.
I want to create a route which can change while the program is running.
Example : app.get('/',function(req,res){/*Something here*/}; This is a normal route.
I want to replace the '/' with a variable which can be replaced with a random number. After that I'll create a qrcode with a nodejs module and the user who scans this qrcode will confirm a kind of transaction.
If you understand my idea and you have a solution, I'll take it.
As #Louy said, use parameters:
var getQRCode = require('./yourQRCodeModule');
app.param('qrcode', function(req, res, next, qrcode) {
// qrcode will be "1234" if your request path was "/1234"
console.log('checking qrcode: %s', qrcode);
// get the qrcode from some asynchronous function
getQRCode(qrcode, function callback(err, qrcode) {
// if this number was not a valid dynamic path, return an error from your module
console.log('qrcode was %s', (!err && qrcode) ? 'valid' : 'invalid');
if (err) {
next(err);
} else if (qrcode) {
req.qrcode = qrcode; // object from your module
next();
} else {
next(new Error('failed to load QR code'));
}
});
});
app.get('/:qrcode', function (req, res) {
// req.qrcode will be the object from your module
// if the number was invalid, this will never be called
});
What I'm trying to point out is that you're thinking of this scenario differently than how express approaches the problem. You want a one-time route with a specific qrcode, but these kind of routes don't exist in express. So here's what I understand your ideal solution to look like:
server creates "azjzso1291084JKioaio1" for a qrcode
you register something like app.getOnce("azjzso1291084JKioaio1", function(req, res){...})
first time the request gets called, it's removed from your express router
Here's what I'm suggesting:
server creates "azjzso1291084JKioaio1" for a qrcode
your module stores this qrcode either in a database or in memory, within your module, e.g. var qrcodes = {}; qrcodes["azjzso1291084JKioaio1"] = {some: 'object'};
your app.param asynchronous function based on the example given in step 2 could look like this:
// yourQRCodeModule.js
var qrcodes = {};
qrcodes["azjzso1291084JKioaio1"] = {some: 'object'};
module.exports = function getQRCode(qrcode, callback) {
if (qrcodes[qrcode]) {
var obj = qrcodes[qrcode]; // copy object
delete qrcodes[qrcode]; // remove from memory here
callback(null, obj);
} else {
// invalid path
callback(new Error('invalid QR code'), null);
}
};
Now notice if you request /azjzso1291084JKioaio1 twice, the second time fails. This is how you intend it to work, if I am not mistaken.
I am working with MongoDB using Mongoose. Most of the opeartion works with callback. An error may occur while saving/updating/finding a document. Though we can always check if there an error in callback function (as shown in below code) but I want to know while developing how can we generate error and test these blocks?
Tank.findById(id, function (err, tank) {
if (err) return handleError(err);
tank.size = 'large';
tank.save(function (err) {
if (err) return handleError(err);
res.send(tank);
});
});
Are you familiar with the Error class? Emiting errors with the EventEmitter? Throwing errors with throw?
This link is a fairly extensive overview on how to deal with errors in node.
Assuming your using express, in the case of the example you provided, I would usually create an instance of the Error class doing something like:
exports.findTankById = function(req, res, next) {
var id = req.params.id;
Tank.findById(id, function (err, tank) {
if (err) {
var e = new Error("Failed to find tank");
e.data = err;
// attach other useful data to error class instance
return next(e);
}
return res.status(200).json({ data: tank });
})
});
Then in another part of the application, have a middleware function that catches errors passed by your routes via next(). That function could log the error or doing something more creative. Note that when using new Error(...) you can access the stack using the stack attribute of the Error class (e.g. err.stack). After processing the error, the error handler function would send back an appropriate response.
A simple error handler function could look something like:
app.use(function (err, req, res, next) {
if(err.data) {
// caught operational errors
/* do something to log or process error */
var response = {
type : 'error',
description : err.message // this would use "Failed to get tank" for above example
};
res.status(500).json(response);
} else {
// unexpected errors
var domainThrown = err.domain_thrown || err.domainThrown;
var msg = 'domainThrown: ' + domainThrown + '\n' + err.stack;
console.error('%s %s\n%s', req.method, req.url, msg);
res.set('Connection', 'close');
res.setHeader('content-type', 'text/plain');
res.status(503).send(msg + '\n');
}
});
If you like this approach, I usually define more specific error objects of my own that more or less extend the Error class. Using functions to create the more specific error types limits the need to write out the
var e = new Error("Failed to find tank");
e.data = err;
/* attach other useful data to error class instance */
part every time. Using more specific error objects also forces consistency on how the errors are being formatted. Hope that is helpful,
Craig
I am using a nosql database(cloudant-couchdb) for the first time and it has been going good so far. But I am stuck with these two problems:
I am trying to add views dynamically. I am able to add the first view, but I get errors while trying to insert more views stating Document update conflict. How would it be an 'update' since I am inserting a new view everytime and not updating it?
Is it possible to pass a parameter in a map function? Something like - if(doc.name == someVariable)?
Here is my code below:
app.put("/listSections", function(req, res) {
var module = req.body.name;
var obj = {};
obj[module] = { "map": function (doc) {
if (doc.name == "Developer") {
//emit something
}
}
});
db.insert({views: obj},
'_design/section', function (error, response) {
if (error) {
console.log("error: " + error);
}
console.log("module: " + module );
}
);
My approach was wrong. I just found creating multiple views is a bad practice. So I created one (general) view, and passed queries as parameters that answer my second question (how to pass dynamic parameters). Here is what I did:
app.get("/listSections", function(req, res) {
var moduleId = req.query.id;
db.view('section','getSections', { key: moduleId }, function(err, body)
{
//store values
}
}
Here I pass the moduleId dynamically to my map function that sets the key as moduleId.
Also, I found this link to be pretty useful for couchdb queries - http://sitr.us/2009/06/30/database-queries-the-couchdb-way.html
Is it possible to reuse / call the blueprint function (find/create/update/destory) and just add some items needed for the controllers. I'm sorry if I'm having hard time expressing my question but hopefully my example will help.
Example:
modue.exports = function(){
index: ....,
create: function(req, res){
try{
// i want to call the blueprint here to save some things
create(req, res);
// then do more after creating the record
....
}catch(err){
// want to catch some error here like validation err
// instead of sending it to res.serverErr();
}
}
....
}
//File api/controller/UserController.js
// suppose the model is User under api/models
modue.exports = {
create: function(req,res){
// pass req.query to User Model's Create function, so you dont need to rewrite all
//optional paramters for this overwrite version
User.create(req.query).exec(function(e, r){
if(e){
// !!!try to create in a different way!
}
})
}
}
You need to first copy blueprint folder from sails which is present in node_modules folder
Paste the blueprint folder in you api folder
Then in your controller for e.g UserController include actionUtil for e.g
var actionUtil = require('sails/lib/hooks/blueprints/actionUtil');
module.exports = {
create: function (req, res) {
// paste code from blueprint create.js file
var Model = actionUtil.parseModel(req);
// Create data object (monolithic combination of all parameters)
// Omit the blacklisted params (like JSONP callback param, etc.)
var data = actionUtil.parseValues(req);
// Create new instance of model using data from params
Model.create(data).exec(function created(err, newInstance) {
// Differentiate between waterline-originated validation errors
// and serious underlying issues. Respond with badRequest if a
// validation error is encountered, w/ validation info.
if (err)
return res.serverError({status:500, message:'error', err: err});
// If we have the pubsub hook, use the model class's publish method
// to notify all subscribers about the created item
if (req._sails.hooks.pubsub) {
if (req.isSocket) {
Model.subscribe(req, newInstance);
Model.introduce(newInstance);
}
// Make sure data is JSON-serializable before publishing
var publishData = _.isArray(newInstance) ?
_.map(newInstance, function (instance) {
return instance.toJSON();
}) :
newInstance.toJSON();
Model.publishCreate(publishData, !req.options.mirror && req);
}
// do your after create stuff here
// Send JSONP-friendly response if it's supported
res.ok({status: 200, message: 'ok', results: newInstance});
});
}
}