Node.js: Code Outside the Browser / Building a Basic Server-Side Application (Without Framework)

Building a very Simple API

πŸ“‘ What Is an API?

In this context, an API (Application Programming Interface) is a service from which we can request data. Here, the goal is to let the client request product data for the "Node Farm" project.

(A deeper explanation of APIs comes in a later section of the course β€” this is a practical introduction.)


πŸ—‚οΈ Step 1: Understanding the Data Source (JSON)

  • πŸ“ There's a file called data.json inside a dev-data folder.

  • πŸ“ JSON is a text format similar to JavaScript objects/arrays.

  • πŸ“¦ It contains an array of five product objects, each with properties like id, productName, image, etc.

  • ⚠️ JSON rule: all keys must be strings (in quotes).

This is the data the API will send back to the client when requested.


πŸ›£οΈ Step 2: Adding a New Route (/api)

In the routing logic (from the previous note), add a new condition:

else if (pathName === '/api') {
  res.end('API');
}
  • For now, this is just a placeholder response β€” it will be replaced with real data shortly.


πŸ“– Step 3: Reading the JSON File

πŸ“ Why Use __dirname Instead of .

Previously, we used . as shorthand for a folder path. But there's a catch:

  • πŸ“ . means "the folder you ran node from" β€” and that can change depending on where you're standing in the terminal.

  • πŸ“Œ __dirname always means "the folder where this script file actually lives" β€” no matter where you ran the command from.

Example:

Say your project looks like this:

project/
 β”œβ”€β”€ index.js
 └── dev-data/
      └── data.json

If you run node index.js from inside the project folder, . and __dirname point to the same place β€” so both work fine.

But if you run the same command from outside that folder (say, from your Desktop):

node project/index.js
  • . now refers to your Desktop, not the project folder β€” so ./dev-data/data.json would look in the wrong place and fail.

  • __dirname still correctly points to the project folder, since that's where index.js actually lives β€” so ${__dirname}/dev-data/data.json works no matter what.

That's why __dirname is the safer, more reliable choice.

βœ… Best practice: Use __dirname instead of . for reliable file paths.

const data = fs.readFileSync(`${__dirname}/dev-data/data.json`, 'utf-8');

πŸ’‘ One exception: The require() function (used to import modules) still treats . as the current file's directory β€” we'll cover this later when building custom modules.


πŸ“¦ Step 4: Parsing JSON into JavaScript

JSON data read from a file is just a string β€” to use it as an actual JavaScript object/array, use:

const dataObject = JSON.parse(data);
  • πŸ”„ JSON.parse() converts a JSON string into a real JavaScript object or array.

  • πŸ” You can then console.log(dataObject) to inspect it β€” it will show a proper JavaScript object matching the structure of the JSON file.


🌐 Step 5: Sending the Data as a Response

To send the API response with the correct headers:

const data = fs.readFileSync(`${__dirname}/dev-data/data.json`, "utf-8");
// const dataObj = JSON.parse(data);
// console.log(dataObj);

......

else if (pathName === '/api') {
  res.writeHead(200, { 'Content-Type': 'application/json' });
  res.end(data);
}

πŸ“Œ Key Points

  • res.end() only accepts a string β€” so we send the raw data (the original JSON text) as-is, not dataObj (the parsed JavaScript version).

  • Set Content-Type to application/json (instead of text/html) so the browser knows to treat the response as JSON.

  • Status code 200 means "OK" β€” a successful response.

Test by restarting the server and visiting: localhost:8000/api

This will display all 5 products' data as JSON in the browser


⚑ Reading the File Only Once

Why the File-Reading Code Sits Outside the Server Callback

const data = fs.readFileSync(`${__dirname}/dev-data/data.json`, 'utf-8');
const dataObj = JSON.parse(data);

const server = http.createServer((req, res) => {
  const pathName = req.url;

  if (pathName === '/overview' || pathName === '/') {
    res.end('This is the OVERVIEW');
  } else if (pathName === '/product') {
    res.end('This is the PRODUCT');
  } else if (pathName === '/api') {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(data);
  } else {
    res.writeHead(404, { 'Content-Type': 'text/html' });
    res.end('<h1>Page not found!</h1>');
  }
});

server.listen(8000, '127.0.0.1', () => {
  console.log('Listening to requests on port 8000');
});
  • πŸ“Œ fs.readFileSync(...) sits outside http.createServer(), at the top level.

  • ⚠️ If it were inside the callback, the file would be re-read on every /api request β€” wasteful, since the data never changes.

    } else if (pathName === '/api') {
    		const data = fs.readFileSync(`${__dirname}/dev-data/data.json`, 'utf-8');
    		const dataObj = JSON.parse(data);
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(data);
      } else {
  • βœ… Kept at the top level, it's read just once, at startup. Every request after that reuses the same data already in memory.

πŸ€” Why readFileSync Here?

  • This line runs only once, at startup β€” not per request β€” so a brief blocking pause causes no harm.

Could We Use Async Instead?

  • Technically yes, but it's trickier: since async doesn't wait, server.listen() could run before data finishes loading, causing errors on early requests.

  • You'd need to nest server.listen() inside the async callback to avoid that.

  • Since blocking briefly at startup is harmless anyway, sync is simpler here.