Topic 15 of 18 · Node Expert

Topic 5 : JSON Data

Lesson TL;DRTopic 5: JSON Data 📖 7 min read · 🎯 intermediate · 🧭 Prerequisites: workingwithasynchronousprogramming, buildingahttpserverwithnodejsusinghttpapis Why this matters Here's the thing — once your Node...
7 min read·intermediate·json · node-js · express · rest-api

Topic 5: JSON Data

📖 7 min read · 🎯 intermediate · 🧭 Prerequisites: working-with-asynchronous-programming, building-a-http-server-with-node-js-using-http-apis

Why this matters

Here's the thing — once your Node.js app starts talking to other parts of a system, like an Express server or a React frontend, everything travels as data. And that data almost always looks the same: JSON. It's the universal language between servers and browsers, between files and APIs. If you've ever seen curly braces with key-value pairs in a file or a network response, that was JSON. In this lesson, we'll learn how to read it, write it, serve it from a server, and fetch it on the frontend — the full journey from disk to browser.

What You'll Learn

  • Understand what JSON is and how to read and write JSON files using Node's fs module
  • Build a full CRUD REST API in Express that accepts and responds with JSON
  • Test API endpoints using Postman
  • Fetch JSON data from an Express API inside a React application using Axios

The Analogy

Think of JSON as the standard shipping label format used by every courier in Vizag. No matter which warehouse a package leaves from or which doorstep it arrives at, every courier reads the same label layout — sender, recipient, contents list — without needing a decoder ring. JSON.stringify() is the label printer that converts your JavaScript object into that universal format, and JSON.parse() is the warehouse scanner that reads the label back into something the application can work with. Because every system in the city agreed on this one format, packages — or data — flow without friction from servers to files to browsers and back again.

Chapter 1: Introduction to JSON

JSON (JavaScript Object Notation) is a lightweight, text-based data interchange format that is easy for humans to read and write and easy for machines to parse and generate. It is based on JavaScript object syntax and is the de-facto standard for transmitting data in web applications.

A JSON document is built from two structures:

  • Objects — unordered key/value pairs wrapped in {}
  • Arrays — ordered lists of values wrapped in []

Valid JSON value types are: string, number, boolean, null, object, and array.

{
    "name": "Alice",
    "age": 30,
    "email": "alice@example.com",
    "skills": ["JavaScript", "Node.js", "React"]
}

Two native JavaScript methods handle the conversion between JSON text and JavaScript objects:

MethodDirectionUsage
JSON.parse(str)JSON string → JS objectAfter reading from a file or HTTP response
JSON.stringify(obj, null, 2)JS object → JSON stringBefore writing to a file or sending a response

The third argument to JSON.stringify (2) is the indentation level — use it when you want human-readable output.

Chapter 2: Reading and Writing JSON Files

Before reaching for a database, many Node.js applications persist small datasets in plain JSON files. The built-in fs module handles both directions.

Reading a JSON File

readJsonFile.js:

const fs = require('fs');

fs.readFile('data.json', 'utf8', (err, data) => {
    if (err) {
        console.error('Error reading file:', err);
        return;
    }
    const jsonData = JSON.parse(data);
    console.log('JSON data:', jsonData);
});

fs.readFile delivers the raw file contents as a UTF-8 string. JSON.parse converts that string into a live JavaScript object you can traverse and manipulate.

node readJsonFile.js

Writing to a JSON File

writeJsonFile.js:

const fs = require('fs');

const jsonData = {
    name: 'Bob',
    age: 25,
    email: 'bob@example.com',
    skills: ['Python', 'Django', 'Flask']
};

fs.writeFile('output.json', JSON.stringify(jsonData, null, 2), 'utf8', (err) => {
    if (err) {
        console.error('Error writing file:', err);
        return;
    }
    console.log('File has been saved.');
});

JSON.stringify(jsonData, null, 2) converts the object to a pretty-printed JSON string before handing it to fs.writeFile. The resulting output.json file is human-readable.

node writeJsonFile.js

File Flow Diagram

flowchart LR
    A[data.json on disk] -->|fs.readFile| B[Raw UTF-8 string]
    B -->|JSON.parse| C[JavaScript object]
    C -->|JSON.stringify| D[Formatted JSON string]
    D -->|fs.writeFile| E[output.json on disk]

Chapter 3: Using JSON with Express

Reading and writing files is useful, but most real-world Node.js applications expose JSON data over HTTP. Express makes this straightforward: the express.json() middleware parses incoming JSON request bodies, and res.json() serializes response objects automatically.

Step 1: Set Up an Express Application

server.js:

const express = require('express');
const app = express();
const PORT = process.env.PORT || 3000;

// Middleware to parse JSON data
app.use(express.json());

// Sample data
let users = [
    { id: 1, name: 'Alice', age: 30, email: 'alice@example.com' },
    { id: 2, name: 'Bob', age: 25, email: 'bob@example.com' }
];

// GET endpoint to return all users
app.get('/users', (req, res) => {
    res.json(users);
});

// POST endpoint to add a new user
app.post('/users', (req, res) => {
    const newUser = req.body;
    newUser.id = users.length + 1;
    users.push(newUser);
    res.status(201).json(newUser);
});

// PUT endpoint to update a user
app.put('/users/:id', (req, res) => {
    const userId = parseInt(req.params.id);
    const updatedUser = req.body;
    const userIndex = users.findIndex(user => user.id === userId);
    if (userIndex === -1) {
        return res.status(404).json({ error: 'User not found' });
    }
    users[userIndex] = { id: userId, ...updatedUser };
    res.json(users[userIndex]);
});

// DELETE endpoint to delete a user
app.delete('/users/:id', (req, res) => {
    const userId = parseInt(req.params.id);
    users = users.filter(user => user.id !== userId);
    res.status(204).send();
});

app.listen(PORT, () => {
    console.log(`Server running on port ${PORT}`);
});
node server.js

Step 2: Testing the Endpoints with Postman

With the server running, the class used Postman to exercise each route.

1. GET /users

  • URL: http://localhost:3000/users
  • Method: GET
  • Send the request to retrieve all users. Expect a JSON array in the response body.

2. POST /users

  • URL: http://localhost:3000/users
  • Method: POST
  • Body (JSON):
{
    "name": "Charlie",
    "age": 28,
    "email": "charlie@example.com"
}
  • Send the request to add a new user. Expect HTTP 201 Created with the new user object (including its assigned id).

3. PUT /users/1

  • URL: http://localhost:3000/users/1
  • Method: PUT
  • Body (JSON):
{
    "name": "Alice Updated",
    "age": 31,
    "email": "alice.updated@example.com"
}
  • Send the request to update the user with ID 1. Expect the full updated user object in the response.

4. DELETE /users/2

  • URL: http://localhost:3000/users/2
  • Method: DELETE
  • Send the request to delete the user with ID 2. Expect HTTP 204 No Content — no body.

Request/Response Lifecycle

sequenceDiagram
    participant C as Client (Postman / React)
    participant M as express.json() middleware
    participant R as Route handler
    participant D as In-memory users[]

    C->>M: HTTP request with JSON body
    M->>R: req.body parsed as JS object
    R->>D: Read or mutate users[]
    D->>R: Returns data
    R->>C: res.json() → HTTP response with JSON body

Chapter 4: Fetching JSON Data in a React Application

The final leg of the data journey is the frontend. the trainer walked the class through spinning up a React app that consumes the Express API using Axios — a promise-based HTTP client with a cleaner API than the browser's native fetch.

Step 1: Create a New React Project

npx create-react-app json-data-demo
cd json-data-demo
npm install axios

Step 2: Fetch Data in a Component

src/components/Users.js:

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function Users() {
    const [users, setUsers] = useState([]);
    const [loading, setLoading] = useState(true);
    const [error, setError] = useState(null);

    useEffect(() => {
        axios.get('http://localhost:3000/users')
            .then((response) => {
                setUsers(response.data);
                setLoading(false);
            })
            .catch((error) => {
                setError(error);
                setLoading(false);
            });
    }, []);

    if (loading) {
        return <div>Loading...</div>;
    }

    if (error) {
        return <div>Error: {error.message}</div>;
    }

    return (
        <div>
            <h1>Users</h1>
            <ul>
                {users.map((user) => (
                    <li key={user.id}>
                        {user.name} - {user.email}
                    </li>
                ))}
            </ul>
        </div>
    );
}

export default Users;

useEffect with an empty dependency array fires once after the component mounts — the right place for a one-time data fetch. axios.get returns a promise; response.data already holds the parsed JavaScript object (Axios parses JSON automatically).

Step 3: Integrate the Component into the App

src/App.js:

import React from 'react';
import './App.css';
import Users from './components/Users';

function App() {
    return (
        <div className="App">
            <header className="App-header">
                <h1>JSON Data Demo</h1>
            </header>
            <Users />
        </div>
    );
}

export default App;

Step 4: Run the React Application

npm start

Navigating to http://localhost:3000 in a web browser displays the list of users fetched from the Express API. Make sure your Express server is still running on port 3000 — or update the Axios URL if you moved it to a different port.

🧪 Try It Yourself

Task: Extend the Express server with a GET /users/:id endpoint that returns a single user by ID. Then add a UserDetail component in React that calls this endpoint when you click a user's name in the list.

Success criteria:

  • Clicking "Alice" in the browser renders { id: 1, name: 'Alice', age: 30, email: 'alice@example.com' } in the page (or a formatted card).
  • Clicking a non-existent ID (e.g., /users/999) shows a "User not found" message — both in the Express 404 response and as an error state in React.

Starter snippet — Express route:

app.get('/users/:id', (req, res) => {
    const userId = parseInt(req.params.id);
    const user = users.find(u => u.id === userId);
    if (!user) {
        return res.status(404).json({ error: 'User not found' });
    }
    res.json(user);
});

🔍 Checkpoint Quiz

Q1. What does JSON.stringify(obj, null, 2) do differently from JSON.stringify(obj)?

A) It converts only the first two keys of the object
B) It pretty-prints the output with 2-space indentation
C) It limits the JSON depth to 2 levels
D) It removes all null values from the output


Q2. Given this Express handler, what HTTP status code and body does a DELETE /users/99 request receive when user 99 does not exist?

app.delete('/users/:id', (req, res) => {
    const userId = parseInt(req.params.id);
    users = users.filter(user => user.id !== userId);
    res.status(204).send();
});

A) 404 { "error": "User not found" }
B) 200 []
C) 204 with no body — the filter simply changes nothing
D) 500 Internal Server Error


Q3. In the React Users component, why is the useEffect dependency array left empty ([])?

A) It causes the effect to re-run whenever users state changes
B) It prevents the component from ever re-rendering
C) It ensures the fetch runs exactly once after the initial mount
D) It is a syntax error; useEffect requires at least one dependency


Q4. You need to read a JSON config file synchronously at server startup so that the rest of the boot sequence can use its values immediately. Which fs method should you use, and why?

A1. B — The third argument is the indent level; 2 causes the output to be formatted with two spaces per nesting level, making the JSON file human-readable. Without it, the output is a single compact line.

A2. C — The handler unconditionally filters the array and responds 204. Because Array.filter returns a new array that excludes no elements when the ID isn't found, the operation is a silent no-op. If you want a 404 when the user doesn't exist, you must check before filtering.

A3. C — An empty dependency array [] tells React "run this effect once, after the first render, and never again." It is the functional-component equivalent of componentDidMount. Adding state variables to the array would retrigger the fetch whenever they changed.

A4. Use fs.readFileSync('config.json', 'utf8') followed by JSON.parse(...). The synchronous variant blocks the event loop until the file is fully read, which is acceptable at startup (before the server begins handling requests) but would be a performance problem inside a request handler.

🪞 Recap

  • JSON is a text format for structured data built on key/value objects and ordered arrays, with two core methods: JSON.parse() and JSON.stringify().
  • Node's fs.readFile + JSON.parse and fs.writeFile + JSON.stringify form the read/write pattern for JSON files on disk.
  • The express.json() middleware unlocks req.body for incoming JSON, while res.json() serializes outgoing responses automatically.
  • A full CRUD Express API uses GET, POST, PUT, and DELETE routes with appropriate status codes (200, 201, 204, 404).
  • Axios in React fetches JSON from an API inside useEffect, populates state via setUsers, and handles loading and error states gracefully.

📚 Further Reading

Like this topic? It’s one of 18 in Node Expert.

Block your seat for ₹2,500 and join the next cohort.