Fetch API, JSON Handling & ES6 Modules
Modern JavaScript applications rarely hardcode static arrays inside one massive file. Instead, code is organized into clean reusable ES6 Modules using export and import, while live data flows back and forth between client UI and backend servers in JSON format via the browser fetch() API.
1. Working with JSON Serialization
JSON (JavaScript Object Notation) is the universal lightweight text format for exchanging structured data across APIs. Note that valid JSON always wraps object property keys in double quotes and does not allow trailing commas or comments:
JSON.parse(jsonString): Parses raw JSON text from an API orlocalStorageinto live JavaScript objects/arrays.JSON.stringify(value): Converts a JavaScript object into a JSON string before saving to storage or sending inside aPOSTrequest body.
// Persist user UI preferences safely in browser storage
const prefs = { theme: "light", fontSize: 16 };
localStorage.setItem("cr_prefs", JSON.stringify(prefs));
const restored = JSON.parse(localStorage.getItem("cr_prefs") || "{}");
2. Making GET and POST Requests with fetch()
A GET request retrieves data, while a POST request sends form or JSON payloads alongside explicit headers:
async function toggleBookmark(lessonId) {
const response = await fetch("/api/toggle_bookmark.php", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
},
body: JSON.stringify({ lesson_id: lessonId })
});
if (!response.ok) {
throw new Error(`Request failed (${response.status})`);
}
return await response.json();
}
Notice how setting headers informs the backend server whether your body is formatted as JSON (application/json) or traditional form fields (FormData, where the browser handles multipart boundaries automatically—never set Content-Type manually when sending FormData).
3. Organizing Large Apps with ES6 Modules
Breaking utilities, API clients, and DOM components into separate files keeps variables out of the global window namespace and clarifies dependencies:
// mathHelpers.js (Named exports + default export)
export const clamp = (val, min, max) => Math.min(Math.max(val, min), max);
export default function formatPercent(part, whole) {
return `${Math.round((part / whole) * 100)}%`;
}
// app.js (Importing cleanly)
import formatPercent, { clamp } from "./mathHelpers.js";
In browser HTML, load the entry point using <script type="module" src="app.js"></script>. Module scripts automatically execute in strict mode, defer execution until the HTML DOM finishes parsing, and isolate top-level variables to each file cleanly.
4. Defensive JSON Parsing & Cross-Origin (CORS) Requests
Calling JSON.parse() on corrupted user storage, empty server bodies, or HTML error pages throws a synchronous SyntaxError that halts your UI unless guarded inside try / catch:
function safeParseJson(rawText, fallback = {}) {
if (!rawText) return fallback;
try {
return JSON.parse(rawText);
} catch (err) {
console.warn("Invalid JSON payload ignored:", err.message);
return fallback;
}
}
- Same-origin vs. CORS: Fetching endpoints on your own domain works seamlessly out of the box, whereas requests to external API domains require the remote server to send explicit
Access-Control-Allow-Originheaders in its HTTP response. - Static server requirement for modules: Testing
<script type="module">via rawfile://URLs triggers browser CORS security errors—always preview multi-file modular apps over a local HTTP server (like XAMPP, Vite, or VS Code Live Server).
5. Hands-On JSON & Fetch Challenge & Key Takeaways
Run the JavaScript snippet below, wrap JSON.parse() inside a safeParseJson() helper, add a fourth topic to the parsed topics array without mutating the JSON string directly, and serialize the updated object back with pretty indentation using JSON.stringify(obj, null, 2).
- Always check
response.okafterfetch(): Remember that HTTP 404 or 500 error responses resolve rather than reject—explicitly inspectif (!response.ok)and throw contextual errors before parsing response JSON. - Guard deserialization carefully: Wrapping
JSON.parse()insidetry / catchprotects client widgets cleanly from unexpected HTML maintenance responses or stale malformed browser storage entries. - Embrace native ES6 modules: Splitting helpers, API callers, and UI renderers across explicit
export/importboundaries prevents global scope collisions and keeps growing frontends cleanly testable.
// Parsing, transforming, and serializing JSON data
const serverResponse = '{"course":"modern-javascript","status":"ok","topics":["Fetch","JSON","Modules"]}';
const parsed = JSON.parse(serverResponse);
console.log("Course:", parsed.course);
console.log("Topics:", parsed.topics.join(", "));
const outgoingPayload = JSON.stringify({ action: "enroll", course: parsed.course });
console.log("Serialized JSON:", outgoingPayload);