i have a node/express app, and need to account for if users don't enter a stationId param in the Url. Ive been looking at all sorts of regex built in to express, adding express-validator but couldnt get it to work for middleware. what would be the best way to do this? is the something in express or a third party validator?
The stationId is an mix of letters and numbers 1491TH it is used to call a second API to get information about the station entered in the Url. Im trying to work out how to use a validator to check if the param is blank, or if its not in the format i want.
import express from 'express'
import axios from 'axios'
import { cleanseLocation } from './utils.js'
const PORT = 3000
const app = express()
app.get('/:stationId/asset',(req, res) => {
const stationId = cleanseLocation(req.params.stationId)
const resp = await axios.get(`https://online-api/id/stations/${stationId}`)
res.send(resp)
})
app.listen(PORT, () =>
console.log(`The node API is running on: http://localhost:${PORT}.`)
)
When you define a route like:
app.get('/:stationId/asset', ...)
You are using a wildcard for the first path segment. It will match ANYTHING in the first path segment. So, it will match all of these:
/play/asset
/1238576/asset
/%20/asset
It will not match:
/asset
because that's just one path segment.
If you have specific rules for what is and isn't a valid stationId, you can implement a check for those inside the route handler:
app.get('/:stationId/asset',(req, res) => {
if (some logic to check req.param.stationId) {
res.send('working');
} else {
res.status(404).send("Invalid stationId");
}
});
If you need further help with how to implement stationId checking, you will have to disclose exactly how you would tell if it's a valid stationId or not.
is the something in express or a third party validator?
What mechanism to use for implementing validation depends entirely upon how you determine whether it is or isn't a valid stationId. You would have to explain that algorithm or method for us to help further.
The stationId is an mix of letters and numbers 1491TH it is used to call a second API to get information about the station entered in the Url. Im trying to work out how to use a validator to check if the param is blank, or if its not in the format i want
It already can't be blank. It won't match the route if there's no stationId at all.
So, if you are looking for a sequence of letters and numbers, you can just use a regex and define the route such that it will only match the route definition if you get a legal format for a stationId:
app.get('/:stationId([A-Za-z0-9]+)/asset', ...)
FYI, here's a helpful article with good example on using regex in Express routes. The full doc for what you can do with a regex in a route definition is here in the path-to-regexp documentation which is the module Express uses for this feature.
Related
I am using Restify for creating REST APIs. I have created the route in such format /ConfigDetails/:name/:id where name and id are optional.
If none of the two params is given, all configdetails will be fetched;
If name is given, config details for that name will be returned;
If id is given, config details for that id will be returned;
My question is, how do I make them optional? With the current route I've defined, unless all two parameters are present, it will not be able to be resolved and will fall into the default route.
As per my understanding restify don't support '?'
Can anyone help me on this.
I think you can't do this in restify. I have a work around which my work for you.
router.get('/ConfigDetails/:name/:id', handler)
router.get('/ConfigDetails/:name', handler)
router.get('/ConfigDetails', handler)
function handler(req, res, next) {
const {name = '', id = ''} = req.params
// your code here
}
As for REST API best practices, you should not put optional path parameters. It should be either in query params or request body for non GET requests.
const route = Router();
router.get('user/:UserId');
router.post('user/:UserId');
router.put('user/:UserId');
I have the above routes for user REST api's, i wanted to have a user by location api. so i tried something like below.
router.get('user/getUserByLocation');
The problem is it calls the router.get('user/:UserId'); as it finds the getUserByLocation as UserId.
I changed the router.get('user/getUserByLocation'); to router.get('getUserByLocation'); it works.
My question is , is the above solution the best approach or i need to use the router.get('user'); with querystring to get the getUserByLocation.
You can declare in router that :UserId should be a number (and not a string) using (\\d+). This way user/getUserByLocation shouldnt match your route user/:UserId:
const route = Router();
router.get('user/:UserId(\\d+)');
router.post('user/:UserId(\\d+)');
router.put('user/:UserId(\\d+)');
router.get('user/getUserByLocation');
You have to configre getUserByLocation before router.get('user/:UserId'). Change the order of your code. Configure router.get('user/getUserByLocation') first.
In express, the first matching request handler is executed, so if router.get('user/getUserByLocation') is configured after router.get('user/:UserId'). That will get called before that.
const route = Router();
router.get('user/getUserByLocation');
router.get('user/:UserId');
router.post('user/:UserId');
router.put('user/:UserId');
If I have two REST endpoints:
app.get('/something/:id', ...handlers);
app.get('/something/else', ...handlers);
And I send a request to http://host:port/something/else
Is there a way to make Express router execute the endpoint with absolute path first (/something/else) before executing the one that matches the query params (/something/:id)?
I understand that I can reverse the order of invocation and specify the endpoint with query params last. But logically speaking, absolute path should take priority over query params and I believe that's the default behaviour for Koa.js
Just put the more specific route first and the wildcard route second. Routes are matched in order and the first one that matches handles the request and the others are not then processed. So, put the more specific route for /something/else before the /something/:id and you will see the /something/else route work properly when that's the URL.
// put wildcard route last and more specific route definitions first
// routes are matched in the order they are defined
app.get('/something/else', ...handlers);
app.get('/something/:id', ...handlers);
This does raise the question why you have designed this potential conflict into your URL scheme in the first place. You've essentially overloaded the id namespace and have reserved at least one id value for your own use. This can be managed by careful ordering of the route definitions, but it would generally be better if you didn't have this conflict in your URL design in the first place.
Is there a way to make Express router execute the endpoint with absolute path first ('/something/else') before executing the one that matches the query params ('/something/:id')?
Yes, define the more specific route first.
I understand that I can reverse the order of invocation and specify the endpoint with query params last. But logically speaking, absolute path should take priority over query params and I believe that's the default behaviour for Koa.js
You asked about Express. It matches routes in the order you've defined them. It doesn't try to guess which route it "thinks" you want to match first. It lets you define that exactly via the order of your route definitions.
I don't know Koa.js well, but there is this in the doc for Koa2: Middleware is now always run in the order declared by .use() (or .get(), etc.), which matches Express 4 API.
There are no specific route matching rules for express.js to match the routes.It goes and try to match every registered route with incoming request path and calls route handlers for all matched paths. Thus the following code will work.
app.get('/something/:id', (req, res, next) => {
console.log(`Calling with param ${req.params.id}`);
next(); // if you remove next from here it will not call the rest of the handlers
});
app.get('/something/else', (req, res, next) => {
console.log(`Calling with else`);
next();
});
Output:
Thus the only way to make sure the routes match exactly, define routes in their specific order.
app.get('/something/else', ...handlers);
app.get('/something/:id', ...handlers);
I have an nodejs server with express and mongoose and I'd like to use method GET for search accoring to criteria which I'd like to provide as JSON object does anyone can help me how to implement it?
Or maybe should I use POST or PUT to make it?
http://hostname/modelname?criteria1=1&criteria2=2
router.route('/modelname')
.get(function (req, res, next) {
req.query.criteria1 === 1 // true
req.query.criteria2 === 2 // true
});
If you are unsure of what HTTP VERB you'd want to use,
POST - is primarily used for creating resources on the server
PUT - is used to update an existing resource
GET- to retrieve a resource
I would use GET in this case
GET http://myhost.com?q=word1+word2+word3+word4
app.get('*', function(req, res) {
// do something with the req.query.q array
});
You've got two options - using HTTP GET params or encoding whole criteria as JSON string and passing that string.
First option, by using params:
?crit[foo.bar][$eq]=abc&crit[foo.baz][$ne]=def
You can read it in nodejs/express via req.query.crit. But this is bad idea because there's no way of retaining data types. For example number 1 becomes string "1". Don't use it - MongoDB is data type sensitive so query {"foo.bar":{$eq:1}} is completely different from {"foo.bar":{$eq:"1"}}
Second option is to urlencode JSON criteria:
?critJSON=%7B%22foo.bar%22%3A%7B%22%24eq%22%3A%20%22abc%22%7D%2C%22foo.baz%22%3A%7B%22%24ne%22%3A%20%22def%22%7D%7D
And parse it on nodejs/express side:
var crit = JSON.parse(req.query.critJSON)
This way your data types are retained and it will work as expected.
Say I've got a few GET routes on my Express application:
// music albums
app.get('/api/albums', routes.albums.getAlbums);
app.get('/api/albums/:id', routes.albums.getAlbum);
app.get('/api/albums/artwork', routes.albums.getAlbumArtwork);
and I attempt to hit them using the follow jQuery AJAX snippet:
$("#retrieveAlbumArtwork").on("click", function() {
$.ajax({
url: "api/albums/artwork",
type: "GET",
data: {
artist: $("#albumArtist").val(),
title: $("#albumTitle").val()
},
// ... callbacks and such
For some reason, this call hits the second handler, with the /:id parameter, instead of the explicit /artwork route. Swapping them like so makes them function as expected:
// music albums
app.get('/api/albums', routes.albums.getAlbums);
app.get('/api/albums/artwork', routes.albums.getAlbumArtwork);
app.get('/api/albums/:id', routes.albums.getAlbum);
Can someone explain exactly why this is happening? I would assume Express would be smart enough to identify an id param (/albums/23453243) versus a querystring (/albums/artwork?artist=artistName&title=albumTitle) and route appropriately, but this doesn't seem to be the case?
No it isn't. :id will match anything. So /api/albums/artwork is totally valid for that match. Express does support RegExp match also. So you could make an explicit numeric matching route using RegExp.
Another option is using app.param as explained in the API documentation here: https://expressjs.com/en/api.html#app.param
This allows you to define matching params for the router so you could have a URL like /api/albums/:albumId where :albumId has to be numeric, you could also validate an albumId at this point if you wished too.
But in all, the second way you are doing it fairly normal, generally I put static routes at the top, then dynamic routes, catch all, then error handlers.