Express: defining REST API with sub-resources? - node.js

I implemented a REST API via Express.
I got a list of suppliers, and for each one list of inventory.
the URL went:
/Supplier/Inventory{supplierId} (e.g http://MyServer/Supplier/Inventory/237 to get inventory list of supplier 237)
So... pretty standard stuff: in my server.js I use: (and I simply here but you guys get the idea)
const routeInventory = require('./routes/Inventory');
...
app.use('/api/Supplier/Inventory', routeInventory);
and in routes file:
outer.route('//:id').get(ShowInventory);
and the function would be something like:
exports. ShowInventory= (req, res, next) => {
const iSupplierId=req.params.id;
console.log('ShowInventory for SupplierId:' + iSupplierId);
...
}
very simple. req.params.id gives me the param I need from the URL.
So far so good. But! somebody here insists that since Inventory is a sub-entity of supplier, the URL should be:
/Supplier/{supplierId}/Inventory
so two questions:
Are they correct? is that the standard?
no matter how much I play with the express code, I can't get the {supplierId} parameter when its in the middle of the URL... how is that done?
Thanks Much

Yes, if you request subresources of a certain resource then you should indicate an id of a resource and only after that indicate subresource name:
/supplier/{supplierId}/inventory
As of routing this can be achived like this:
router.get('/supplier/:supplierId/inventory', ShowInventory)
that way you can access supplierId like this:
const supplierId = req.params.supplierId

Related

REST Api implementation in Firebase Functions

I am currently implementing Rest API standards in firebase functions. I am searching for an option for passing id as params. But I am not sure where my current Implementation was sufficient to change like that.
My code looks like this.
export const user = functions.https.onRequest(userModule);
and the URL will be like
https://firebaseapp<baseurl>/user
I need to pass the user id in the URL(not as query param)
expected URL
https://firebaseapp<baseurl>/user/{userId}
Is there any way to make the URL look like the above in firebase functions?
You can use Request object from Express to access the path parameters using params property as shown below:
app.get("/user/:userId", (req, res) => {
console.log("Path params", req.params); // { userId: "USER_ID" }
res.json(req.params);
}));

Paramaters in a Node.js HTTP server

I'm trying to make a simple HTTP module, similar to express, to help learn how to use HTTP.
When working with express I often use parameters such as:
app.get('/id/:id' (req, res) => {console.log(req.params.id); stuff})
I was wondering-
Is it possible just using HTTP?
If it isn't able to be done with just HTTP, then how would you go about creating it?
Your question is a little bit confusing but I think what you mean is how to implement an HTTP Router in pure javascript instead of relying on a framework like express.
If that's the case I support your initiative 100%! It's really important to understand what is going on behind the scenes.
A good way to really understand a good way to do so would be by reading the source code of a good router that is already out there.
You could study the Express router's source code but I recommend you to go play with find-my-way which is a dedicated router that you can use with HTTP only without any other framework.
Yes but you need to process it manually.
Assuming you're using node's http module, the information you are looking for is in req.url but it includes the whole url path.
For example, you want to parse /id/:id, and the browser is making a request to http://your.server/id/100?something=something. Then the value of req.url will be:
/id/100?something=something
But if you are writing an HTTP module from scratch using the net module then you need to know the format of an HTTP request. Basically an HTTP request looks like a text file with the following format:
GET /id/100?something=something HTTP/1.1
Host: your.server
The header section ends with double newlines. Technically it should be \r\n\r\n but \n\n is also acceptable. You first need to get the url from the first line of the request between the protocol (GET in the example above but can be POST, PUT etc.) and the HTTP version number.
For anyone interested in writing a HTTP server from scratch I always recommend James Marshall's excellent article: https://www.jmarshall.com/easy/http/. It was originally written in the late 90s but to this day I haven't found a clearer summary of the HTTP protocol. I've used it myself to write my first HTTP server.
Now you have to write code to extract the 100 from the string.
If you are doing this manually, not trying to write a framework like Express, you can do it like this:
const matches = req.url.match(/id\/([^\/?]+)/);
const id = matches[1];
If you want to write a library to interpret the /id/:id pattern you can maybe do something like this (note: not an optimized implementation, also it behaves slightly differently to express):
function matchRoute (req, route) {
const [ urlpath, query ] = req.url.split('?'); // separate path and query
const pathParts = urlpath.split('/');
const routeParts = urlpath.split('/');
let params = {};
for (let i=0; i<pathParts.length; i++) {
if (routeParts[i].match(/^:/)) { // if route part starts with ":"
// this is a parameter:
params[routeParts[i].replace(':','')] = pathParts[i];
}
else {
if (routeParts[i] !== urlParts[i]) {
return false; // false means path does not match route
}
}
// if we get here (don't return early) it means
// the route matches the path, copy all found params to req.params:
req.params = params;
return true; // true signifies a match so we should process this route
}
}
// Test:
let req.url = '/id/100';
let result = matchRoute(req, '/id/:id');
console.log(result, req.params); // should print "true, {id:100}"

What to do when NodeJS rest api is sendind status 404 while using parameters?

I am having a strange problem while writing my api. I am using Nodejs and express. The problem occurs when i try to use GET with parameters.
This is my routes code
router.get('/addFriends/:email', (req, res, next) =>{
const email = req.params.email;
UserSchema.find({email: email}, { "friendsPending.emailSender": 1, _id : 0}, (err, data) =>{
if(err){
res.status(404).send(err);
}else{
res.status(200).send(data[0]);
}
});
});
This is my call in Postman : /users/addFriends?email=a
When running this call, server returns 404 status. It happened even when i tested it with another get call.Any comments are appriciated, however other POST and GET calls work normally (they use body not parameters). Thanks
You mixed query params and url params. To make your example working, you need to use /addFriends/my-email#gmail.com instead of /users/addFriends?email=a.
If you need to send emails via query params (everything after ?) use req.query in your controller instead of req.params.email.
This route definition:
router.get('/addFriends/:email', ...);
expects a URL that looks like this:
/addFriends/someEmail
And, you would use req.params.email to refer to the "someEmail" value in the URL path.
Not what you are trying to use:
/addFriends?email=a
If you want to use a URL such as (a URL with a query parameter):
/addFriends?email=a
Then, you would have a route definition like this:
router.get('/addFriends', ...);
And, then you would refer to req.query.email in the route handler. Query parameters (things after the ? in the URL) come from the req.query object.
In Express, route definitions match the path of the URL, not the query parameters.
when you use /addFriends/:param you force the router to match any request tha have a part in path as :param.For example:
/users/addFriends/toFavorates // will **match**
/users/addFriends/toFavorates?email=a // will **match**
/users/addFriends?email=a // will **not** match
if you want to make :param as optional part of url path put a ? after it
/addFriends/:param?
it will tell express route that :param is an optinal part. See this question
express uses path-to-regexp for matching the route paths. read the documentation for more options

NodeJS - Wrong Express route being triggered

I have the following routes available for a backend...
admin.js
router.get('/contents', ...); // GET /admin/contents
router.get('/:adminID', ...); // GET /admin/[adminID]
router.put('/:adminID', ...); // PUT /admin/[adminID]
router.get('/', ...); // GET /admin
router.post('/', ...); // POST /admin
.. but in testing, the following:
PUT /admin/contents
triggers the PUT /admin/[adminID] route. But "contents" is not an ID. I understand why this is happening (i.e. it fits into the pattern), but I'm not sure what the best/common solution is to this? Ideally, I'd like it to recognize that "contents" is not an ID, and is in fact just attempting to use an unavailable endpoint.
I could use something like...
router.use('/contents', require('./admin-contents'));
but I'd prefer to limit each top-level endpoint to a single file, opposed to spreading it across so many.
Worst-case scenario, it will look for an admin with ID: "contents", and return "admin not found", but I'd prefer it to return 404, because that is not an available endpoint for /admin.
Edit #1
To clarify, adminID is a mix of letters and numbers, with either occurring in any position in the string. A regex will not work.
Also, the only route for /admin/contents is GET. Having to implement blank routes for all the other methods (PUT, PATCH, DELETE, etc) is not ideal either.
You can provide a regex after a paramenter name in the route, to avoid that scenario.
router.put('/:adminID(\\d+)', (req, res) => {
console.log(req.params.adminID); // I'm a number
});
Now adminID must be a number, otherwise it won't enter the route.
While that's not directly documented on express routing, since express uses path-to-regexp we can see their documentation for this:
And it's documented in Custom Matching Parameters
const regexpNumbers = pathToRegexp('/icon-:foo(\\d+).png')
// keys = [{ name: 'foo', ... }]
regexpNumbers.exec('/icon-123.png')
//=> ['/icon-123.png', '123']
regexpNumbers.exec('/icon-abc.png')
//=> null
UPDATE
Your suggestion of checking for even just one number in a known-length
string should work,
app.put('/:adminID((?:\\w+(?<=\\d+)(?:\\w+)?))', (req, res) => {
// I have at least 1 number
// I can have or not alpha-numeric characters
res.send(req.params.adminID);
});
The regex uses Postive lookbehind assertions which are supported without any flag since Node.js 9.11.2. So if you're using an older version, either upgrade or use the --harmony flag to run it.
You can take advantage of the fact that node interprets handlers in order:
app.put('/admin/contents', (req, res) => res.send('contents'))
app.put('/admin/:adminId', (req, res) => res.send('id'))
When you enter admin/contents, contents is returned, for any other url admin/whatever id is returned.

multiple routes with differents params, calling the same ressource

Is it possible with expressjs to have multiple routes calling the same resource, something like that:
app.get('/users/:user_id', users.getOne)
app.get('/users/:username', users.getOne)
I would like to be able to call users.getOne whichever params (:user_id or :username) is used in the get request.
In the users.getOne function, how can I determine wich one was used and build my query according to it?
exports.getOne = function(req, res){
var queryParams = ? // I need help here
Users
.find(queryParams)
...
Thanks!
Possibly related: express.js - single routing handler for multiple routes in a single line
From express's view, both of those routes will match the same set of request URLs. You only need one of them and you can name it to make more sense:
app.get('/users/:key', users.getOne);
//...
// http://stackoverflow.com/a/20988824/266795
var OBJECT_ID_RE = /^[a-f\d]{24}$/i;
exports.getOne = function(req, res) {
var conditions = {_id: req.params.key};
if (!OBJECT_ID_RE.test(req.params.key)) {
conditions = {username: req.params.key};
}
Users.find(conditions)...
If you end up wanting this pattern in many routes throughout your code base, you can extract it into a /users/:user param and use app.param as per #alex's answer, but encapsulate the code to locate the user and stick it on to req.user so the actual route handler can just assume the user has been properly found and loaded by the time it executes, and 404 handling can be centralized as well.
Those are in fact, from express's view, the same route.
No, they are not. One route has :user_id parameter, another one has :username.
This would be a proper solution:
var OBJECT_ID_RE = /^[a-f\d]{24}$/i;
app.param('user_id', function(req, res, next, value, name) {
if (OBJECT_ID_RE.test(value)) {
next()
} else {
next('route')
}
})
app.get('/users/:user_id', users.getOne)
app.get('/users/:username', users.getOne)
app.param set the prerequisite for the route to be called. This way when user_id matches a pattern, first route gets called, otherwise second one.

Resources