Express.js Mastery / Express: Let’s Start Building The Natours API!

Starting Our API: Handling GET Requests

🌍 The Project: Natours

  • 🧳 A tour-booking application: view tours, book them, create accounts, log in, leave reviews, etc.

  • πŸ—οΈ The course builds this in two phases:

    1. First: build the API (working with raw data only).

    2. Later: build the dynamically rendered website on top of it.

  • 🎯 Building the API first makes it easier to focus purely on learning Express, MongoDB, and Mongoose without also worrying about UI/design at the same time.


πŸ‘€ Quick Look at the Final API (via Postman)

  • 🌐 Base endpoint: localhost:3000/api/v1

  • πŸ“₯ GET /api/v1/tours β†’ returns all tours (matches the data seen on the rendered site).

  • πŸ” GET /api/v1/tours/:id β†’ returns just that one tour, with much more detail (start locations, tour guides β€” which reference other IDs, locations, reviews, etc.).

  • There's also a users resource, but it requires authentication to access.

πŸ’‘ This is just a preview β€” these advanced features get built up gradually through the course.


πŸ› οΈ Implementing: GET /api/v1/tours

πŸ”Ή Step 1: Remove/comment out the old test route

  • πŸ’¬ The earlier / GET/POST test routes are commented out (not deleted) since they're no longer needed.


πŸ”Ή Step 2: Set up the route with API versioning

app.get('/api/v1/tours', (req, res) => {
  // route handler
});
  • The callback function passed to .get() is called the route handler.

  • v1 in the URL = API versioning β€” good practice.

    • If breaking changes are needed later, a new v2 endpoint can be created without disrupting anyone still using v1.

    • Versioning could also be done via subdomain, but including it in the URL path is simpler.


πŸ”Ή Step 3: Read the tours data β€” outside the route handler, at top-level

  • Import the fs module

  • Core modules are conventionally required first, at the top of the file.

const fs = require('fs');
  • Data lives in dev-data/data/tours-simple.json β€” a JSON array of tour objects (similar structure to node-js course "node farm" project data).

  • Access JSON data in app.js

const tours = JSON.parse(
  fs.readFileSync(`${__dirname}/dev-data/data/tours-simple.json`)
);

πŸ“‚ Reading the JSON File

  • ❓ Why read the file at the top of the file instead of inside the route handler?

    • πŸš€ Top-level code runs only once when the server starts.

    • ⚑ This avoids reading the file on every incoming request.

    • βœ… Since it's outside the request handler, using readFileSync() is acceptable.

  • πŸ“– fs.readFileSync(path)

    • Reads the file synchronously and returns its contents.

  • πŸ“ __dirname

    • Refers to the absolute path of the current file's directory.

    • Helps build the correct file path regardless of where the app is run.

  • πŸ”„ JSON.parse()

    • Converts the JSON string into a JavaScript object or array.


πŸ”Ή Step 4: Send the data back using the JSend format

app.get('/api/v1/tours', (req, res) => {
  res.status(200).json({
    status: 'success',
    results: tours.length,
    data: {
      tours: tours,
    },
  });
});
  • βœ… res.status(200)

    • Sets the HTTP status code.

    • Although 200 is the default, explicitly setting it is considered good practice.

  • βœ… status

    • Indicates whether the request was successful.

    • Possible values:

      • 🟒 success β†’ Successful request (2xx)

      • 🟑 fail β†’ Client error (4xx)

      • πŸ”΄ error β†’ Server error (5xx)

  • πŸ“‚ data

    • Contains the actual response payload.

    • Typically named after the resource being returned (e.g., tours).

  • ⭐ results (Optional)

    • Shows the total number of items returned.

    • Useful when returning an array, but unnecessary for a single object.

    • Not part of the official JSend specification.


πŸ”Ή Step 6: Test in Postman

  • 🌐 URL: 127.0.0.1:3000/api/v1/tours

  • πŸ“₯ Method: GET

  • βœ… Result:

{
  "status": "success",
  "results": 9,
  "data": {
    "tours": [ ...9 tour objects... ]
  }
}

βœ… Key Takeaway

  • πŸ“„ This is currently a file-based API β€” data is read from a local JSON file, not a database (that comes later with MongoDB/Mongoose).

  • πŸ“ Pattern established:

    • πŸ“– Read data once, at the top level, outside any route handler.

    • πŸ›£οΈ Use app.get(url, callback) to define the route + handler.

    • πŸ“¦ Respond using res.status(code).json({ status, results, data: { resourceName } }) β€” the JSend-style envelope.

  • ➑️ Next step: implement the POST request to allow creating new tours.