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
fsmodule - 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:
| Method | Direction | Usage |
|---|---|---|
JSON.parse(str) | JSON string → JS object | After reading from a file or HTTP response |
JSON.stringify(obj, null, 2) | JS object → JSON string | Before 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 Createdwith the new user object (including its assignedid).
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 Express404response 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()andJSON.stringify(). - Node's
fs.readFile+JSON.parseandfs.writeFile+JSON.stringifyform the read/write pattern for JSON files on disk. - The
express.json()middleware unlocksreq.bodyfor incoming JSON, whileres.json()serializes outgoing responses automatically. - A full CRUD Express API uses
GET,POST,PUT, andDELETEroutes with appropriate status codes (200,201,204,404). - Axios in React fetches JSON from an API inside
useEffect, populates state viasetUsers, and handles loading and error states gracefully.
📚 Further Reading
- JSON specification (json.org) — the source of truth on valid JSON syntax and types
- Node.js
fsmodule docs — complete reference forreadFile,writeFile, and their synchronous variants - Express
res.json()docs — explains how Express serializes and setsContent-Typeautomatically - Axios documentation — full API reference including interceptors, request cancellation, and config defaults
- ⬅️ Previous: Building a HTTP Server with Node.js using HTTP APIs
- ➡️ Next: File System