Welcome to the Treehouse Community
Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.
Looking to learn something new?
Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.
Start your free trialAkshay Alok
7,857 PointsHow would my code identify between /quotes/:id and /quotes/random?
For /quotes/random to work I had put it above /quotes/:id, is there no other way? I want a route to /quotes/:id only when /:id is a number if the url after the /quotes/ is not a number, then I don't want to route to /quote/:id. Can this be done? Or would I have to code some sort of logic inside the route /quotes/:id...
JASON LEE
17,352 PointsI like Ben McMahan's solution by ordering the routes accordingly such that the explicit routes are handled first, and the /quotes/:id
route parameter isn't 'short-circuiting' the /quotes/random
route.
However, if you want to know how to handle it otherwise, here's how I did it. This was my original solution as I thought the challenge involved using /quotes/random
route.
// Send a GET request to /quotes/:id READ(view) a quote
router.get('/quotes/:id', asyncHandler ( async (req, res, next) => {
const id = req.params.id;
// to redirect it if the url is .../quotes/random, have to RETURN next() so it does not execute the rest of this function
if (id === "random") return next();
const quote = await records.getQuote(parseInt(id))
if (quote) {
res.json(quote);
} else {
res.status(404).json({message : "Quote not found"})
}
}))
// Send a GET request to /quotes/quote/random to READ(view) a random quote
router.get('/quotes/random', asyncHandler( async (req, res, next) => {
const theQuotesObj = await records.getQuotes();
const quotes = theQuotesObj.records;
const quote = quotes[Math.floor(Math.random() * quotes.length)]
res.status(200).json(quote);
}))
5 Answers
Brad Chandler
15,301 PointsBased on your own comment, you could check if the :id (req.params.id) is a number by trying to coerce it to a number. If it is not, then you could call next(); to pass on to the next middleware and eventually return a 404 error.
router.get('/quotes/:id', (req, res, next) => {
if (isNaN(parseInt(req.params.id))) {
next();
} else {
//perform response stuff here
}
});
The if statement parses the id as a number, if it fails it will return NaN (not a number) which is caught by isNaN() and returns true if the id is not a number.
I think the answer to your original question is quite simple. You made a mistake in your route. The random route is supposed to be /api/quotes/quote/random.
I was wondering how the /quotes/:id route didn't get mixed up with the /quotes/quote/random route when it seems that /quote/random would fill in as the :id in the first route.
It seems that if there is a "/" within the id and it isn't expected, the router simply moves on to the next middleware.
Try testing out a get request to /api/quotes/quoterandom or /api/quotes/quote and you will see a 404 error if you have a try...catch block set up to catch an empty quote object. Then if you make it /api/quotes/quote/random it will work.
James Crosslin
Full Stack JavaScript Techdegree Graduate 16,882 PointsI just realized I misunderstood Akshay, so let's handle what they're talking about: making sure a "/quotes/random" request does not get resolved by "/quotes/:id". For this, we just need a simple conditional:
router.get(
'/quotes/:id',
asyncHandler(async (req, res) => {
const { id } = req.params
if(id === "random") res.redirect('/quotes/random')
const quote = await records.getQuote(id)
if (quote) return res.json(quote)
throw new Error("Could not find quote")
})
)
And that's all! But honestly, I would just put "/quotes/random" before "/quotes/:id" if you don't want to change the path to "/quotes/quote/random". A request sent to a node server checks the first router listed to the last. We as developers should use that functionality to our advantage. It's not a bug, it's just how it natively works and it allows us to set up proper gateways for our routes. So just pop that "/quotes/random" to the top of your routes.js (or at least above "/quotes/:id") and your problems are solved!
Tyler McDonald
Full Stack JavaScript Techdegree Graduate 16,700 PointsBut honestly, I would just put "/quotes/random" before "/quotes/:id" if you don't want to change the path to "/quotes/quote/random". A request sent to a node server checks the first router listed to the last. We as developers should use that functionality to our advantage. It's not a bug, it's just how it natively works and it allows us to set up proper gateways for our routes.
This is the best answer in my opinion. Why add extra code when we could structure the routes in such a way that we don't have to perform conditional checks?
codingchewie
8,764 PointsIf I am understanding what you're asking is you want if someone passes in a bad req to automatically generate a random quote? Meaning you want to change:
localhost:3000/api/quotes/dfdfsdfsd
instead of getting a message: 'Quote not found.'
you want some random post returned? To do this you can leave /quotes/quote/random
and just modify the code for '/quotes/:id'
. Instead of returning an error you can always return something:
router.get(
'/quotes/:id',
asyncHandler(async (req, res) => {
let quote
quote = await records.getQuote(req.params.id)
if (quote) return res.json(quote)
quote = await records.getRandomQuote()
return res.json(quote)
}),
)
James Crosslin
Full Stack JavaScript Techdegree Graduate 16,882 Pointscodingchewie's answer isn't a bad one, but it is repeated code and infringes on the territory of another route we created. I would suggest adding a redirect like this for the use case they presented.
router.get(
'/quotes/:id',
asyncHandler(async (req, res) => {
const quote = await records.getQuote(req.params.id)
if (quote) return res.json(quote)
res.redirect('/quotes/random')
})
)
Brian Wright
6,770 PointsAlthough this would work, I would be more inclined to check the req.params for the string 'random'. to handle my redirect. That way you are not relying on the getQuote function to return a false value for the redirect to happen. If the getQuote() function throws an error for an invalid id, which is what the string 'random' would be, it would be handled by the Express error middleware and would not redirect.
JASON LEE
17,352 PointsJames Crosslin - I'm surprised the res.redirect('/quotes/random')
would not cause an infinite loop.
I would think the redirected url to '/quotes/random' goes back to being handled by router.get('/quotes/:id')
??
Ben McMahan
7,922 PointsComing from a lot of years writing routes in the Laravel PHP framework, I have always gone with putting all routes with a direct URL above those that expect a parameter. It's easier to read and flows without having to check if a parameter is present or if it matches a type or specific value.
Express routing works the same way. In my solution, the router will first run /quotes/random
because it is explicitly defined. Otherwise, it will skip to the next route with a parameter.
// GET request to return a random quote
router.get('/quotes/random', asyncHandler(async (req, res) => {
const randomQuote = await records.getRandomQuote();
if (randomQuote) {
res.json(randomQuote);
} else {
res.status(404).json({message: "Sorry, there are no quotes"});
}
}));
// Send a GET request to /quotes/:id to READ a quote
router.get('/quotes/:id', asyncHandler(async (req, res) => {
const quote = await records.getQuote(req.params.id);
if (quote) {
res.json(quote);
} else {
res.status(404).json({ message: "Quote not found" });
}
}));
Akshay Alok
7,857 PointsAkshay Alok
7,857 PointsI just want the word 'random' from /quotes/random to NOT be appointed as the value to req.params.id in /quotes/:id