I have the following Node.js Express routes
app.get('/api/admin/elements/:type', elements.getElementsByType);
app.get('/api/admin/elements/:_id', elements.getElement);
the elements funtions are:
getElementsByType : function(req, res) {
element.find({
type : req.params.type
}, function(err, elements) {
if (err) {
return err;
}
res.json(elements);
});
},
getElement : function(req, res) {
var file = "/thatulborooj-data/elements/" + req.params._id + ".json";
jsonfile.readFile(file, function(err, obj) {
if (err) {
res.json([]);
} else {
res.json(JSON.parse(obj));
}
});
}
when I all $http.get('/api/admin/elements/' + scope.element._id) fro angularJS it always all the first get, how can I make difference between them or set parameter type
express.js doesn't know the name of the variable that you pass to it, because it is a webserver which listen to some addresses.
In your case the first route says:
If someone goes on /api/admin/elements/{{whatever}} call the function elements.getElementsByType() and assign whatever to the variable type
The second route says the same thing, just to call another function and assign the value to the var _id.
A possible solution is to have two different routes:
app.get('/api/admin/elements/type/:type', elements.getElementsByType);
app.get('/api/admin/elements/id/:_id', elements.getElement);
Another possible solution is, if you are sure id is always an int and type is never an int is to call just one function and choose what to do checking the param type:
app.get('/api/admin/elements/:value', elements.getElementsByType);
getElementsByType : function(req, res) {
if (isNaN(req.params.value)) {
// this is a type
} else {
//this is an id
}
But I want to highlight it is important you understand why your example doesn't work.
I cannot explain how web works in just one answer, but let me highlight some things:
express.js listens on your server to request to a port, as a webserver. You can say to express.js which routes you want to listen: all routes that aren't specified will reply with 404 (or 405 if you ask for a method that doesn't exist on a resource that exists, like POST on /api/admin/elements/{{whatever}} in your example).
So, when you specify a route, you have two components:
a fixed part, which is the address, and it is always the same (like /api/admin/elements/), and a part which value can change.
You say to express.js which part can change using the : notation. For express.js, whatever is in an URL between the /: and the next / has to be passed in the req.params object with the name you specify.
Let's do some examples:
/a/:a/b/:b/c/:c
This address will response to each call you do to `/a/{{param1}}/b/{{param2}}/c/{{param3}}
So you will have a req.params object with these 3 values:
req.param = {
a: 'param1',
b: 'param2',
c: 'param3',
}
You do not like a, b, and c as names? Me neither, so let's change that:
/a/:artemis/b/:betelgeuse/c/:cassidy
Then the req.params object will be:
req.param = {
artemis: 'param1',
betelgeuse: 'param2',
cassidy: 'param3',
}
But for express.js it is the same thing, it cannot choose what it is basing only on the value.
Now, an example a bit more complicated:
if you have
/a/:a and /a/b for express.js can be a bit confusing (but also for whoever uses your APIs, so please do not do this).
Now, how this works is up to the order you declared the routes.
If you did /a/:a and then /a/b the function associated to /a/b will never be called. express.js takes the address requested and check each route if it has a match. So if you call /a/b express.js finds /a/:a (that means an /a/ and a thing after this and thinks the b is a valid value for the variable a).
But if you declare the /a/b before the /a/:a express.js will reach before the /a/b (that means a route that is exactly /a/b) then it will be matched first.
Assuming that _id should match an ObjectId, you can make the route handler explicitly match those:
app.get('/api/admin/elements/:_id([a-f0-9]{24})', elements.getElement);
app.get('/api/admin/elements/:type', elements.getElementsByType);
Just be sure that the _id route is declared before the (more general) type route.
Caveat: this breaks if you use types that match ObjectId's.
Related
Hi I'm trying to update my database with function that returns a number
$scope.sum = function()
{
return $scope.inp + $scope.points;
};
this function will update the record in object points, column name and id 1:
$scope.addPoint = function() {
PointService.addPoint($scope.sum, 1)
.then(function(result) {
$scope.inp = 0;
getMyPoints();
});
}
addPoint = function(id,points)
{
return $http.put(getUrlForId(1),points,name);
}
the error is: Error details: Cannot convert type 'int' to 'System.Collections.Generic.Dictionary'
the data type of the field is Float.
Any idea what is wrong with the code?
you are passing function reference to PointService.addPointer(),
use this:
$scope.addPoint = function() {
PointService.addPoint($scope.sum(), 1) // NOT PointService.addPoint($scope.sum, 1)
.then(function(result) {
$scope.inp = 0;
getMyPoints();
});
}
this will execute your function and pass the output (id parameter) to addPoint function, further, for more safe side, you can return Number from $scope.sum() i.e.
$scope.sum = function()
{
return Number($scope.inp + $scope.points);
};
This looks like an issue with how you're contacting Backand. You use the following code to send your points over:
addPoint = function(id,points)
{
return $http.put(getUrlForId(1),points,name);
}
This is an older version of calling the Backand API that is manually constructing a PUT request, and putting "points" and "name" as the "Data" and "config" parameters to $http. With an object update via PUT, you'll need to provide the updates as an object. So if you wanted to update the points and the name of the object (and I'm doing some assuming based upon what I can tell from the code snippet above), you'd need to encapsulate these properties in an object that has the following general format:
{
"field_name_1":"new value_1",
"field_name_2":"new value_2",
etc ...
}
This should then be sent as the body of the request. So, for your code, change it to the following and see if this helps:
addPoint = function(id,points)
{
return $http.put(getUrlForId(1),{points: points, name: name});
}
To give more info on why you're seeing this particular error, Backand is depending on this JSON format in the body. Our platform should definitely do more validation (and I'll create a ticket for the devs to handle non-conforming input more gracefully), but at the moment we simply take the body of the request, convert it to a dictionary object, then begin the requested operation. As your code above sends only "1.0" as the body, this fails the conversion into a dictionary, causing the stack exception and the issue you are seeing.
As a note, we offer a new SDK that encapsulates these HTTP methods, performing the authentication header generation and HTTP messaging for you, providing promises to handle responses. You can find it on our Github page at https://github.com/backand/vanilla-sdk. To make the same call using the new SDK, the code would resemble the following:
backand.object.update("your object name", 1, {name: name, points: points})
.then(function(response){
console.log(response.data);
});
In traditional REST API, we should define our API like this:
GET /api/things -> get all
POST /api/things -> create
GET /api/things/:id -> get one
PUT /api/things/:id -> update
DELETE /api/things/:id -> delete
How should i define another 'get one' endpoint for querying data by any other field other than id? For example:
GET /api/things/:title -> get one by title (this sure does not work since the api isn't aware of URL parameter names)
GET /api/things/title/:title ? this does not work for me at all..
GET /api/things?title=whatever (this cannot be defined at all. When i write this in my index.js:
router.get('?title=whatever', controller.getByTitle);
I get this:
SyntaxError: Invalid regular expression: /^?title=whatever\/?$/: Nothing to repeat
at RegExp (native)
ID should be an unique identifier. Given one ID, you should return one resource at most. That's why an URI like GET /api/things/:id makes sense.
For other properties which may or may not be unique, you can have more than one result, so use the GET /api/things endpoint and pass query parameters : /api/things?title=mytitle.
app.get('/api/things', function (req, res) {
console.log(req.query.title); //mytitle
ThingModel.find({
title: req.query.title
}, function (err, things) {
res.send(things);
});
});
First of all I want to mention that I have been digging around a lot for this. I am unable to find a simple and straight forward answer even in the docs. (Call me dumb if you will, in case it IS mentioned in the docs! I can't seem to find it anyway.)
The thing is, I want to make a PUT request to a URL of the form
app.constant('URL_REL_VENDOR_PRODUCTS', '/api/vendor/:vendorId/products/:productId');
But I do not want to put the vendorId parameter in the request payload. My service layer looks something like this:
services.factory('VendorProductService', function($resource, UserAccountService, URL_BASE, URL_REL_VENDOR_PRODUCTS) {
return $resource(URL_BASE + URL_REL_VENDOR_PRODUCTS, {
vendorId: UserAccountService.getUser().vendorId,
id: '#id'
}, {
update: { method: 'PUT' }
});
});
I know that instead of the vendorId: UserAccountService.getUser().vendorId I could have written something along the lines vendorId: '#vendorId' but then that pollutes my payload doesn't it?
I don't want to keep the mechanism I am already using in the example as the mechanism does not work when you switch accounts i.e.,if the UserAccountService.getUser() is updated. Basically I'm having to reload the entire page to get the service initialized again.
In short, the question is, as the title suggests, how do I set the path parameter vendorId without using a service like the one in the snippet and also without modifying the payload?
Make the parameter value a function:
services.factory('VendorProductService', function($resource, UserAccountService, URL_BASE, URL_REL_VENDOR_PRODUCTS) {
return $resource(URL_BASE + URL_REL_VENDOR_PRODUCTS, {
vendorId: function () {
return UserAccountService.getUser().vendorId
},
id: '#id'
}, {
update: { method: 'PUT' }
});
});
From the Docs:
paramDefaults (optional)
Default values for url parameters. These can be overridden in actions methods. If a parameter value is a function, it will be executed every time when a param value needs to be obtained for a request (unless the param was overridden).
-- AngularJS $resource API Reference
I am building an application prototype and try to mock the REST web-services.
Here is my code:
var mock = angular.module('mock', ['ngMockE2E']);
mock.run(function($httpBackend){
users = [{id:1,name:'John'},{id:2,name:'Jack'}];
$httpBackend.whenGET('/users').respond(users);
$httpBackend.whenGET(new RegExp('\\/users\\/[0-9]+')).respond(users[0]);
}
Everything is ok, my resource User.query() returns all users, and User.get({id:1}) and User.get({id:2}) returns the same user (John).
Now to improve my prototype, I would like to return the appropriate user, matching the good id.
I read in the angular documentation I should be able to replace the RegExp URI by a function. The idea is to extract the id from the url to use it in respond method.
I then tried this:
$httpBackend.whenGET(new function(url){
alert(url);
var regexp = new RegExp('\\/users\\/([0-9]+)');
id = url.match(regexp)[1];
return regexp.test(url);
}).respond(users[id]);
The problem is the url parameter is always undefined. Any idea to achieve my goal?
By using new function(url) your app tries to instantiate a new object from your anonymous function and pass that new object as the first argument of the $httpBackend.whenGET() call.
Of course, at the time of calling whenGET() no URL is provided, thus it is always undefined.
You should pass the function itself (and not an object instanciated using the function). E.g.:
$httpBackend.whenGET(function (url) {
...
}).respond(users[id]);
UPDATE:
After some more digging it turned out that the option to pass a function as the first argument to whenGET was added in version 1.3.0-beta.3. The docs you were reading probably referred to the latest beta version, while you were using an earlier version.
(Note that even versions 1.3.0-beta.1 and 2 did not provide this option.)
Without getting into much detail, responsible for verifying a matching URL is MockHttpExpectation's matchUrl method:
function MockHttpExpectation(method, url, data, headers) {
...
this.matchUrl = function(u) {
if (!url) return true;
if (angular.isFunction(url.test)) return url.test(u);
if (angular.isFunction(url)) return url(u); // <<<<< this line does the trick
return url == u;
};
The line if (angular.isFunction(url)) return url(u); is the one that gives the option to directly pass a function and was added in version 1.3.0-beta.3 (as already mentioned).
But, if you still want to pass a function to a previous AngularJS version, you could "trick" angular into believing you passed a RegExp, by providing an object with a test method.
I.e. replace:
.whenGET(function (url) {...})
with:
.whenGET({test: function (url) {...}})
See, also, this short demo.
I found a solution by using a function in the respond part instead of the when part:
$httpBackend.whenGET(new RegExp('\\/users\\/[0-9]+')).respond(
function(method, url){
var regexp = new RegExp('\\/users\\/([0-9]+)');
var mockId = url.match(regexp)[1];
return [200, users[mockId]];
}
});
I have a Cloud Endpoints method that looks like this:
//HTTP POST
#ApiMethod(name = "hylyts.insert")
public Hylyt insertHylyt(#Named("url") String url, Hylyt hylyt, User user)
throws OAuthRequestException{
log.info("Trying to save hylyt '"+hylyt+"' with id '"+hylyt.getId());
if (user== null) throw new OAuthRequestException("Your token is no good here.");
hylyt.setArticle(getArticleKey(url, user));
ofy().save().entity(hylyt);
return hylyt;
}
I call it from the Javascript Client Library using this:
gapi.client.hylytit.hylyts.insert({PARAMS}).execute(callback);
Now, if I structure {PARAMS} as suggested in the docs (second example),
{
'url': url,
'resource': {
'hylyt': {
'contentType': 'application/json',
'data': hylyt
}
}
}
I get a null object in the endpoint (not to mention that the whole point of this library is to make these calls simple, which this structure clearly violates).
When I structure {PARAMS} as these answers suggest,
{
'url': url,
'resource': hylyt
}
I get a null object in the endpoint again. The correct syntax is this:
{
'url': url,
'id': hylyt.id
'text': hylyt.text
}
Which just blows my mind. Am I doing this all wrong? Is this a bug? Is it only happening because gapi is also passing the auth token in the background?
Yes, I could use the request syntax instead, but, again, why even use the library if it's just as complex as making the XHRs in pure javascript? I wouldn't mind the complexity if Google explained in the docs why things are happening. But the docs, paraphrased, just say use these methods and the auth, CORS, and XHR magic will happen behind closed doors.
Is the API method correctly recognized as POST method?
The resource parameter which is sent as POST body won't work correctly in a GET request.
The way it looks you are actually sending a GET request with the Hylyt properties in the query string.
To make sure you can change the method annotation to this:
#ApiMethod(name = "hylyts.insert", httpMethod = HttpMethod.POST)
Yup, agreed it's a bug. caused me great pains as well.
So i guess the work around is to create a combined object to pass to your api all named and un named parameters. Rather than hardcode each.. a quick loop might be better.
var param = {};
param["url"] = url;
for (var prop in hylyt) {
param[prop] = hylyt[prop];
}
gapi.client.hylytit.hylyts.insert(param).execute(callback);
That mashing together of parameters / objects can become a slick function if you really want.. but it's a band aid for what I'd consider a defect.
I see in the related question (cloud endpoints resource attribute for transmitting named params & body not working), you actually logged a defect.. Good stuff. Though there still appears no movement on this one. fingers crossed for someday!
The bug has been resolved. The correct syntax is
gapi.client.hylytit.hylyts.insert({url: url}, hylyt).execute(callback);