Express.js Mastery / Express: Letโ€™s Start Building The Natours API!

Responding to URL Parameters

๐ŸŽฏ Goal

  • ๐Ÿ“‹ Currently, /api/v1/tours returns all tours.

  • ๐Ÿ” This lecture implements getting one tour by ID: /api/v1/tours/5.


๐Ÿ”น Step 1: Understand URL Parameters (Variables in the URL)

  • Letโ€™s create a new endpoint

app.get('/api/v1/tours/:id', (req, res) => {
  console.log(req.params);
  res.status(200).json({ status: 'success' }); // placeholder for now
});
  • The 5 in /api/v1/tours/5 is a variable part of the URL โ€” it could be any ID.

  • To handle a variable segment, define a route parameter using a colon:

  • :id defines a parameter named id โ€” could be named anything (:var, :x, etc.)

  • These URL variables are called parameters, and they're automatically collected into req.params.


๐Ÿ”น Step 2: Inspect req.params

  • Hitting /api/v1/tours/5 logs:

{ id: '5' }
  • โž• Multiple parameters can be defined at once:

app.get('/api/v1/tours/:id/:x/:y',

โžก๏ธ Hitting /api/v1/tours/5/23/45 gives:

{ id: '5', x: '23', y: '45' }
  • โš ๏ธ All defined parameters must be present in the URL, or the route won't match at all (results in an error / route not found).


๐Ÿ”น Step 3: Optional Parameters

Add a ? after the parameter name to make it optional:

app.get('/api/v1/tours/:id/:x/:y?',)
  • โœ… Now y doesn't have to be included in the URL โ€” if omitted, req.params.y will simply be undefined.

  • ๐Ÿ’กThis example is just for demonstration โ€” the actual route only needs :id


๐Ÿ”น Step 4: Find the Matching Tour Using Array.prototype.find()

app.get('/api/v1/tours/:id', (req, res) => {
  const id = req.params.id * 1; // string โ†’ number conversion trick
  const tour = tours.find((el) => el.id === id);

  res.status(200).json({
    status: 'success',
    data: {
      tour,
    },
  });
});
  • .find() is a plain JavaScript array method (not Node/Express-specific) โ€” loops through the array and returns the first element where the callback returns true.

  • req.params.id is always a string, but tour IDs in the data are numbers โ€” so a comparison would silently fail without conversion.

  • Trick: multiplying a numeric-looking string by 1 (req.params.id * 1) automatically converts it to a number in JavaScript.

  • The callback compares each tour's id to the parsed numeric id from the URL, and .find() returns the matching tour (or undefined if none match).


๐Ÿ”น Step 5: Test It

  • โœ… /api/v1/tours/5 โ†’ returns the tour with id: 5.

  • โœ… /api/v1/tours/2 โ†’ returns the tour with id: 2.

  • โŒ /api/v1/tours/23 โ†’ returns nothing meaningful, but the server still sends a 200 OK status โ€” which is incorrect, since no tour actually exists with that ID.


๐Ÿ”น Step 6: Handle Invalid/Non-Existent IDs (404)

๐Ÿ…ฐ๏ธ Approach A: Check ID against array length first

app.get('/api/v1/tours/:id', (req, res) => {
  const id = req.params.id * 1;

  if (id > tours.length) {
    return res.status(404).json({
      status: 'fail',
      message: 'Invalid ID',
    });
  }

  const tour = tours.find((el) => el.id === id);

  res.status(200).json({
    status: 'success',
    data: { tour },
  });
});
  • return is critical here โ€” it exits the function immediately after sending the 404 response, preventing the code from continuing on to try sending a second response.

  • status: 'fail' is used specifically for 4xx (client-side) errors, as opposed to 'success' (2xx) or 'error' (5xx).

๐Ÿ“š Acknowledged as a simplistic solution โ€” this course project isn't the real production API, so this rough approach (checking against array length) is fine for now for learning purposes. A real app would need more robust ID/input validation.


๐Ÿ…ฑ๏ธ Approach B (Preferred): Check if tour itself is undefined

app.get('/api/v1/tours/:id', (req, res) => {
  const id = req.params.id * 1;
  const tour = tours.find((el) => el.id === id);

  if (!tour) {
    return res.status(404).json({
      status: 'fail',
      message: 'Invalid ID',
    });
  }

  res.status(200).json({
    status: 'success',
    data: { tour },
  });
});
  • If .find() doesn't find a match, tour is undefined โ€” checking if (!tour) catches that case directly, arguably a cleaner approach than pre-checking against array length.

  • โœ… Both approaches produce the same result when tested.


โœ… Key Takeaway

  • ๐Ÿ›ฃ๏ธ Route parameters are defined with :paramName in the URL and are automatically available on req.params.

  • โ“ Parameters can be made optional with a trailing ?.

  • ๐Ÿ”ค Route parameters are always strings โ€” convert to numbers when needed (1 trick works for numeric strings).

  • ๐Ÿ” .find() is a standard JS array method, useful for locating a single matching item.

  • ๐Ÿšซ Always return the correct status code โ€” 404 (not 200) when a requested resource doesn't exist โ€” and use return to stop further code/response execution after sending an error response.