API Incident Lab: Fix a REST API
Diagnose and fix a local order dashboard through realistic REST API incidents.
Introduction
30 Second Summary
A support ticket often starts with a vague message that a page is broken. The real challenge is finding the cause before a rushed fix hides the clues.
In this project, you will repair an order dashboard backed by a local REST API. You will trace each failure with browser evidence, direct API tests, and server logs before writing a LinkedIn-ready account of the investigation.
What You'll Build
You will demo a repaired order dashboard that loads three orders quickly while your investigation connects every symptom to evidence, a root cause, and a verified fix.
By the end of this project, you'll have:
- A working order dashboard that loads three orders from a local API and displays them without the original route, response-contract, or latency failures.
- A repeatable evidence-first troubleshooting method that uses Chrome DevTools, Postman, and server logs to isolate failures before changing code.
- A LinkedIn-ready incident report in linkedin-post.md that explains the symptoms, evidence, root causes, fixes, and verification.
- Secret Mission: Add and investigate a safe HTTP 500 scenario that returns a generic client error while preserving the detailed cause in server logs.
Are there any prerequisites?
You need a supported Windows computer with internet access for the installers. Basic Chrome DevTools familiarity is useful because the project includes setup for every required tool.
Before We Start
This is your moment to commit to an evidence-first troubleshooting process for a broken order dashboard and REST API. You will document each failure so you can share the investigation in a LinkedIn-ready post.
Install and Verify the Incident Toolkit
Your investigation needs dependable evidence from every layer of the REST API. A missing tool could look like an application failure and send the diagnosis in the wrong direction.
This step prepares Google Chrome, Chrome DevTools, Visual Studio Code, Node.js, Postman, and Windows PowerShell. Each tool gets its own readiness check before the incident begins.
In this step, get ready to:
- Prepare the browser and editor for evidence collection.
- Verify the required local JavaScript runtime.
- Prepare an independent client for testing HTTP requests.
Why Start With Browser Evidence?
Chrome DevTools connects a visible page failure to the request that produced it. The request status and response body turn a vague symptom into evidence.
Postman then sends the same request without the frontend. This comparison helps you isolate the failing layer before changing code.
Install Google Chrome and Visual Studio Code
Google Chrome provides the page where you reproduce the incident. Its Network panel captures requests while DevTools remains open.
- Press the Windows key to open Windows search.
- Type Settings and press Enter.
- Select System.
- Select About.
- Find System type under Device specifications.
- Record the displayed processor architecture here: x64-based processor or ARM-based processor.
You should see either x64-based processor or ARM-based processor. This value determines which Node.js installer your computer can run.
- Confirm your computer meets the official Google Chrome requirements. Intel x64 requires Windows 10 or later. ARM requires Windows 11 or later.
- Download Google Chrome from the official Windows installation page.
- Run the downloaded Google Chrome installer.
- Press the Windows key to open Windows search.
- Type Google Chrome and press Enter.
- Press F12 to open DevTools.
- Click Network in DevTools.
You'll see the Network panel ready to record browser requests. That gives the investigation its first evidence surface.
Chrome or DevTools Not Opening?
Confirm that your Windows version meets the requirement for your processor architecture. Run the Chrome installer again if Windows search cannot find the browser.
Select the Chrome window before pressing F12. Some keyboards also require you to hold the function key.
Help me troubleshoot Google Chrome or DevTools on Windows.
Visual Studio Code holds the project files in later steps. Its Windows User setup also adds the editor command to PATH for PowerShell.
- Use the official Visual Studio Code Windows setup guide to download the Windows User setup.
- Run the downloaded Visual Studio Code User setup.
- Complete the installer with its default options.
- Close every open PowerShell window so the updated PATH can apply.
- Press the Windows key to open Windows search.
- Type PowerShell and press Enter.
- Verify the editor command by running:
code .
What Does This Command Prove?
The code command proves PowerShell can find Visual Studio Code through PATH. The period tells the editor to open PowerShell's current folder.
You'll see Visual Studio Code open with the current folder in its Explorer. The editor and terminal can now work together.
PowerShell Cannot Find the Editor?
Close every PowerShell window after installing Visual Studio Code. Open a new PowerShell window before trying the command again.
Run the Windows User setup again if the command still cannot be found. That setup adds Visual Studio Code to PATH.
Help me fix the Visual Studio Code command in PowerShell.
Install Node.js 24.21.0 LTS
Node.js runs the local server used by the incident lab. The required release is v24.21.0 LTS.
The installer must match the processor architecture you recorded: x64-based processor or ARM-based processor.
Before you run this check, predict whether PowerShell will already report the required Node.js version.
- Switch back to the PowerShell window from earlier.
- Check the installed Node.js version by running:
node --version
What Does This Command Check?
The command asks the Node.js executable to print its installed version. The result separates a ready runtime from an outdated or missing installation.
✔️ I see the required LTS version
PowerShell prints v24.21.0. Your runtime is ready to run the incident server.
ⓧ I see an older version
Your existing Node.js release does not match the lab. Install the required LTS release before continuing.
- Match the installer to your recorded architecture: x64-based processor or ARM-based processor.
- Download node-v24.21.0-x64.msi if Windows reports x64-based processor.
- Download node-v24.21.0-arm64.msi if Windows reports ARM-based processor.
- Run the matching MSI installer.
- Complete the installer with its default options.
- Close the current PowerShell window.
- Open a new PowerShell window through Windows search.
- Verify the updated version by running:
node --version
What Should This Confirm?
The new PowerShell session reads the updated PATH. The command should now reach the newly installed Node.js executable.
You'll see v24.21.0 in the output. The older runtime is no longer blocking the lab.
ⓧ Command not found
PowerShell cannot find a Node.js installation. Install the required LTS release that matches your computer.
- Match the installer to your recorded architecture: x64-based processor or ARM-based processor.
- Download node-v24.21.0-x64.msi if Windows reports x64-based processor.
- Download node-v24.21.0-arm64.msi if Windows reports ARM-based processor.
- Run the matching MSI installer.
- Complete the installer with its default options.
- Close the current PowerShell window.
- Open a new PowerShell window through Windows search.
- Verify the installation by running:
node --version
What Should This Confirm?
The new PowerShell session reads the PATH created by the installer. The command should now find Node.js.
You'll see v24.21.0 in the output. Node.js is ready for the local server.
Still Not Seeing the Required Version?
Confirm that you downloaded the installer matching your Windows processor architecture. An x64 installer does not match an ARM-based processor.
Close every PowerShell window after the installation. A console that stayed open can keep using the earlier PATH.
Help me verify my Node.js installation on Windows.
Install Postman and verify the toolkit
Postman acts as an independent HTTP client. It lets you test the local API without depending on the dashboard.
- Download the Postman desktop app by following the official Windows installation guide.
- Run the downloaded .exe installer.
- Complete the Postman installation.
- Press the Windows key to open Windows search.
- Type Postman and press Enter.
- Continue into the lightweight Postman API Client without signing in.
- Click New in the sidebar.
- Choose HTTP.
You'll see a request tab with a Send button. Postman is ready to test the API independently.
Cannot Reach the Lightweight Client?
Confirm that you installed the desktop app from the official Postman page. Continue without signing in so your work remains in the lightweight local client.
Close Postman after installation if its window does not respond. Open it again through Windows search.
Help me open the lightweight Postman API Client.
Before the final check, predict whether every tool will now show its readiness signal.
- Switch back to PowerShell.
- Verify the runtime and editor commands by running:
node --version
code .
What Do These Checks Prove?
- The first command confirms that PowerShell can run the required Node.js release.
- The second command confirms that PowerShell can launch Visual Studio Code through PATH.
You'll see v24.21.0 in PowerShell. You'll also see Visual Studio Code open.
- Switch back to Google Chrome.
- Press F12.
- Click Network.
- Switch back to Postman.
- Click New.
- Choose HTTP.
You'll see the Network panel in Chrome DevTools. You'll also see a Postman request tab with Send available.
Your incident toolkit is ready to collect evidence from the browser, API client, runtime, and editor. Next up, you'll launch the broken order dashboard and capture its first visible failure.
Launch the Broken Order Dashboard
A useful investigation starts with a reproducible symptom. Seeing the user-facing failure first gives you concrete evidence before any diagnosis begins.
Your toolkit is ready. You will now launch a local REST API with Node.js. A deliberately broken dashboard in Google Chrome provides the incident report.
In this step, get ready to:
- Create the api-incident-lab folder with its project files.
- Add the deliberately broken server and dashboard code in Visual Studio Code.
- Launch the local server and capture the visible failure.
Create the project structure
The dashboard has two layers. The server lives in index.js. The browser page lives in public/index.html.
- Switch back to the PowerShell window from earlier.
- Create the project structure on your Desktop by running these commands:
Set-Location -Path ~/Desktop
New-Item -Path api-incident-lab -ItemType Directory
Set-Location -Path api-incident-lab
New-Item -Path public -ItemType Directory
New-Item -Path index.js -ItemType File
New-Item -Path public/index.html -ItemType File
code .
What does this setup do?
- The first command moves PowerShell to your Desktop.
- The next commands create the api-incident-lab folder with its public subfolder.
- The file commands create index.js plus public/index.html.
- The final command opens the current folder in Visual Studio Code.
PowerShell lists the items it created. Visual Studio Code opens with index.js plus the public folder in the Explorer sidebar.
Project files missing?
Check that PowerShell moved into api-incident-lab before it created the files. Run the setup commands again from your Desktop if the folder opened somewhere else.
If Visual Studio Code did not open, confirm that the restarted PowerShell window still recognizes the code command.
Help me fix the PowerShell project setup.
Add the incident-ready files
A controlled incident gives every investigation the same starting evidence. The server code includes a delayed API response with an intentional response-contract mismatch.
- Select index.js in the Visual Studio Code Explorer sidebar.
- Replace its empty contents with this server code:
const http = require('node:http');
const fs = require('node:fs');
const path = require('node:path');
const PORT = 3000;
const orders = [
{ id: 'NW-1042', customer: 'Amina', status: 'Processing' },
{ id: 'NW-1043', customer: 'Jordan', status: 'Packed' },
{ id: 'NW-1044', customer: 'Mei', status: 'Shipped' },
];
function sendJson(response, statusCode, payload) {
response.writeHead(statusCode, { 'Content-Type': 'application/json' });
response.end(JSON.stringify(payload));
}
const server = http.createServer((request, response) => {
console.log(`[request] ${request.method} ${request.url}`);
if (request.method === 'GET' && request.url === '/api/orders') {
setTimeout(() => sendJson(response, 200, { items: orders }), 2000);
return;
}
if (request.url.startsWith('/api/')) {
sendJson(response, 404, { error: 'Route not found' });
return;
}
if (request.method === 'GET' && (request.url === '/' || request.url === '/index.html')) {
const pagePath = path.join(__dirname, 'public', 'index.html');
fs.readFile(pagePath, 'utf8', (error, html) => {
if (error) {
console.error('[incident] Unable to read the dashboard file:', error.message);
sendJson(response, 500, { error: 'Unable to load the application' });
return;
}
response.writeHead(200, { 'Content-Type': 'text/html' });
response.end(html);
});
return;
}
sendJson(response, 404, { error: 'Page not found' });
});
server.listen(PORT, () => {
console.log(`Incident lab running at http://localhost:${PORT}`);
});
How does the server create the incident?
- The built-in Node.js modules create an HTTP server without third-party packages.
- The orders array holds three records for the dashboard.
- The sendJson() helper sends a status code with a JSON response body.
- The /api/orders branch delays its response by 2000 milliseconds.
- That branch returns the records under items for the response-contract incident.
- The remaining branches serve the dashboard or return safe error responses.
- Save index.js.
- Confirm the file remains visible at the top level of the api-incident-lab folder.
Server file looks incomplete?
Confirm that the first line imports node:http. Confirm that the final block calls server.listen().
Help me compare my index.js file with the incident-ready version.
The browser layer calls the server through the Fetch API. Its request path is deliberately set to the incident route before the first launch.
- Select public/index.html in the Explorer sidebar.
- Replace its empty contents with this dashboard code:
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Order Support Dashboard</title>
<style>
:root { color-scheme: dark; font-family: system-ui, sans-serif; background: #0f172a; color: #e2e8f0; }
body { max-width: 760px; margin: 0 auto; padding: 48px 20px; }
main { background: #1e293b; border: 1px solid #334155; border-radius: 16px; padding: 24px; box-shadow: 0 18px 45px rgba(0, 0, 0, 0.3); }
h1 { margin-top: 0; }
button { border: 0; border-radius: 8px; padding: 10px 16px; background: #38bdf8; color: #082f49; font-weight: 700; cursor: pointer; }
#status { min-height: 24px; color: #fbbf24; }
#orderList { display: grid; gap: 10px; padding: 0; list-style: none; }
#orderList li { padding: 14px; border-radius: 8px; background: #0f172a; border-left: 4px solid #38bdf8; }
</style>
</head>
<body>
<main>
<p>Incident Desk</p>
<h1>Order Support Dashboard</h1>
<p>Use the browser, API client, and server logs to explain every failure before changing code.</p>
<button id="reloadButton" type="button">Reload orders</button>
<p id="status">Ready</p>
<ul id="orderList"></ul>
</main>
<script>
const reloadButton = document.getElementById('reloadButton');
const statusMessage = document.getElementById('status');
const orderList = document.getElementById('orderList');
async function loadOrders() {
statusMessage.textContent = 'Loading orders...';
orderList.textContent = '';
try {
const response = await fetch('/api/order');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
data.orders.forEach((order) => {
const item = document.createElement('li');
item.textContent = `${order.id} | ${order.customer} | ${order.status}`;
orderList.appendChild(item);
});
statusMessage.textContent = `Loaded ${data.orders.length} orders.`;
} catch (error) {
console.error('Order load failed:', error);
statusMessage.textContent = `Unable to load orders: ${error.message}`;
}
}
reloadButton.addEventListener('click', loadOrders);
loadOrders();
</script>
</body>
</html>
How does the dashboard report failures?
- The page provides a status message plus a list for order records.
- The loadOrders() function requests /api/order when the page loads.
- The response.ok check converts an unsuccessful status into an error for the page.
- The success path expects an array at data.orders.
- The catch block displays the failure message inside status.
- Save public/index.html.
- Confirm index.html appears inside the public folder.
Dashboard file looks incomplete?
Confirm that index.html sits inside public. Check that the file ends with closing body plus html tags.
Help me compare my dashboard file with the incident-ready version.
Your two files should now match the incident state. Use these tabs if you want one final comparison before launching the server.
✔️ Awesome, I've got everything!
Both files are saved. Your project is ready for its first run.
ⓧ I'd like to double check the full code
Check index.js
const http = require('node:http');
const fs = require('node:fs');
const path = require('node:path');
const PORT = 3000;
const orders = [
{ id: 'NW-1042', customer: 'Amina', status: 'Processing' },
{ id: 'NW-1043', customer: 'Jordan', status: 'Packed' },
{ id: 'NW-1044', customer: 'Mei', status: 'Shipped' },
];
function sendJson(response, statusCode, payload) {
response.writeHead(statusCode, { 'Content-Type': 'application/json' });
response.end(JSON.stringify(payload));
}
const server = http.createServer((request, response) => {
console.log(`[request] ${request.method} ${request.url}`);
if (request.method === 'GET' && request.url === '/api/orders') {
setTimeout(() => sendJson(response, 200, { items: orders }), 2000);
return;
}
if (request.url.startsWith('/api/')) {
sendJson(response, 404, { error: 'Route not found' });
return;
}
if (request.method === 'GET' && (request.url === '/' || request.url === '/index.html')) {
const pagePath = path.join(__dirname, 'public', 'index.html');
fs.readFile(pagePath, 'utf8', (error, html) => {
if (error) {
console.error('[incident] Unable to read the dashboard file:', error.message);
sendJson(response, 500, { error: 'Unable to load the application' });
return;
}
response.writeHead(200, { 'Content-Type': 'text/html' });
response.end(html);
});
return;
}
sendJson(response, 404, { error: 'Page not found' });
});
server.listen(PORT, () => {
console.log(`Incident lab running at http://localhost:${PORT}`);
});
What should match?
Your server must include the delayed /api/orders branch. Its payload must use items.
Check public/index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Order Support Dashboard</title>
<style>
:root { color-scheme: dark; font-family: system-ui, sans-serif; background: #0f172a; color: #e2e8f0; }
body { max-width: 760px; margin: 0 auto; padding: 48px 20px; }
main { background: #1e293b; border: 1px solid #334155; border-radius: 16px; padding: 24px; box-shadow: 0 18px 45px rgba(0, 0, 0, 0.3); }
h1 { margin-top: 0; }
button { border: 0; border-radius: 8px; padding: 10px 16px; background: #38bdf8; color: #082f49; font-weight: 700; cursor: pointer; }
#status { min-height: 24px; color: #fbbf24; }
#orderList { display: grid; gap: 10px; padding: 0; list-style: none; }
#orderList li { padding: 14px; border-radius: 8px; background: #0f172a; border-left: 4px solid #38bdf8; }
</style>
</head>
<body>
<main>
<p>Incident Desk</p>
<h1>Order Support Dashboard</h1>
<p>Use the browser, API client, and server logs to explain every failure before changing code.</p>
<button id="reloadButton" type="button">Reload orders</button>
<p id="status">Ready</p>
<ul id="orderList"></ul>
</main>
<script>
const reloadButton = document.getElementById('reloadButton');
const statusMessage = document.getElementById('status');
const orderList = document.getElementById('orderList');
async function loadOrders() {
statusMessage.textContent = 'Loading orders...';
orderList.textContent = '';
try {
const response = await fetch('/api/order');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
data.orders.forEach((order) => {
const item = document.createElement('li');
item.textContent = `${order.id} | ${order.customer} | ${order.status}`;
orderList.appendChild(item);
});
statusMessage.textContent = `Loaded ${data.orders.length} orders.`;
} catch (error) {
console.error('Order load failed:', error);
statusMessage.textContent = `Unable to load orders: ${error.message}`;
}
}
reloadButton.addEventListener('click', loadOrders);
loadOrders();
</script>
</body>
</html>
What should match?
Your dashboard must call /api/order inside loadOrders(). The catch block must place the error message in the page status.
Launch the dashboard and capture the symptom
The page now becomes your incident report. Your first job is to observe what the user sees plus what the server records.
- Select Terminal from the Visual Studio Code menu bar.
- Select New Terminal.
Before you start the server, do you expect the browser request to succeed or fail?
- Start the server in watch mode by running:
node --watch index.js
What does watch mode do?
The command starts index.js with Node.js watch mode. Node.js restarts the process when a watched file changes.
The terminal remains attached to the running server. Keep it running for the rest of the investigation.
You will see Incident lab running at http://localhost:3000 in the terminal. That message confirms the server is listening.
- Press the Windows key to open search.
- Type Google Chrome.
- Press Enter to open the browser.
- Enter http://localhost:3000 in the address bar.
- Press Enter to load the dashboard.
The dashboard reports Unable to load orders: HTTP 404. The terminal logs a request containing GET /api/order.
You now have a reliable incident to investigate. The browser symptom plus the server request log prove that the failure is reproducible.
Dashboard did not show the 404?
Confirm that the integrated terminal still shows the running server. Check that Chrome is using http://localhost:3000.
If the page loads with a different result, compare both files with the full-code tab. Pay close attention to /api/order inside loadOrders().
Help me reproduce the dashboard's HTTP 404 incident.
The incident is running with a clear symptom. Next up, you will compare browser evidence with an independent API request to isolate the failing route.
Isolate the 404 Route Failure
Your broken dashboard now produces a repeatable 404. The next goal is to trace that symptom to the exact request using Chrome DevTools.
A browser error alone cannot prove the REST API is unavailable. You will compare the browser evidence with a direct Postman request so the route diagnosis rests on matching facts.
In this step, get ready to:
- Inspect the failed browser request in Chrome DevTools.
- Test both API routes independently in Postman.
- Correct the frontend route after proving the root cause.
Inspect the failed browser request
The Network panel records the request that sits behind the dashboard error. Its request details connect the visible symptom to evidence from the browser.
- Switch back to Google Chrome at http://localhost:3000.
- Press F12 to open the last-used DevTools panel.
- Click Network in DevTools.
- Select Fetch/XHR in the request filters.
Before you reload, do you expect the failed request to show a browser problem or a response from the server?
- Reload the dashboard with DevTools open.
- Select the /api/order request in the Network table.
- Click Headers in the request details.
You will see a GET request to /api/order with status 404. This proves the browser reached the server and received a response.
- Click Response in the request details.
The JSON body contains {"error":"Route not found"}. The server is reporting that this specific path has no matching API route.
- Click Initiator in the request details.
You can trace the request back to the frontend fetch call in public/index.html. This connects the server response to the line that initiated it.
What does each piece of evidence prove?
- The Headers evidence identifies the requested route and its status.
- The Response evidence shows how the server handled that route.
- The Initiator evidence links the request to the frontend code that created it.
Test both routes in Postman
A direct API request removes the dashboard from the test. Comparing two routes reveals whether the server is unavailable or one path is incorrect.
- Press the Windows key to open the Windows search bar.
- Type Postman and press Enter to open it.
- Click New in the sidebar.
- Choose HTTP.
- Select GET as the request method.
- Enter http://localhost:3000/api/order in the request URL field.
- Click Send.
You will see status 404 with {"error":"Route not found"}. Postman reproduces the same route failure without using the dashboard.
- Replace the request URL with http://localhost:3000/api/orders.
This request takes roughly two seconds because the incident server delays it, so the quiet wait is expected.
- Click Send.
After the wait, you will see status 200. The response body uses an items property containing the three orders.
Together, these responses prove that the server is running. The singular route in the dashboard is the cause of the 404.
Why use Postman after Chrome?
Chrome shows how the dashboard created the failing request. Postman tests the API without involving the frontend.
When both clients produce matching results, you can isolate the faulty layer before changing code.
Correct the frontend route
The browser evidence and direct API tests point to one line in the frontend. You can now make a focused route correction instead of changing the server.
- Switch back to Visual Studio Code from earlier.
- Select public/index.html in the Explorer sidebar.
- Find this line:
const response = await fetch('/api/order');
What does the current line do?
This line requests the singular /api/order path. Your browser and Postman evidence confirmed that the server returns 404 for this path.
- Replace the singular route with the plural route shown below:
const response = await fetch('/api/orders');
What changed?
The fetch call now targets /api/orders. This matches the server route that returned status 200 in Postman.
- Save public/index.html.
Before you reload, do you think correcting the route is enough to make all three order rows appear?
- Return to Google Chrome from earlier.
- Reload the dashboard with the Network panel open.
- Select the new /api/orders request in the Network table.
After roughly two seconds, you will see status 200 for /api/orders. The dashboard still reports an error instead of displaying the three rows.
That second failure is intentional. The route now works, but the frontend and API disagree about the response contract.
Still seeing a 404?
- Confirm that the fetch path is exactly /api/orders with the final s.
- Save public/index.html before reloading the dashboard.
- Click Reload orders if the browser did not start a fresh request.
Help me diagnose why my corrected /api/orders request still returns 404.
✔️ Awesome, I've got everything!
Great. Double-check that you saved public/index.html with the plural API route.
ⓧ I'd like to double check the full code
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Order Support Dashboard</title>
<style>
:root { color-scheme: dark; font-family: system-ui, sans-serif; background: #0f172a; color: #e2e8f0; }
body { max-width: 760px; margin: 0 auto; padding: 48px 20px; }
main { background: #1e293b; border: 1px solid #334155; border-radius: 16px; padding: 24px; box-shadow: 0 18px 45px rgba(0, 0, 0, 0.3); }
h1 { margin-top: 0; }
button { border: 0; border-radius: 8px; padding: 10px 16px; background: #38bdf8; color: #082f49; font-weight: 700; cursor: pointer; }
#status { min-height: 24px; color: #fbbf24; }
#orderList { display: grid; gap: 10px; padding: 0; list-style: none; }
#orderList li { padding: 14px; border-radius: 8px; background: #0f172a; border-left: 4px solid #38bdf8; }
</style>
</head>
<body>
<main>
<p>Incident Desk</p>
<h1>Order Support Dashboard</h1>
<p>Use the browser, API client, and server logs to explain every failure before changing code.</p>
<button id="reloadButton" type="button">Reload orders</button>
<p id="status">Ready</p>
<ul id="orderList"></ul>
</main>
<script>
const reloadButton = document.getElementById('reloadButton');
const statusMessage = document.getElementById('status');
const orderList = document.getElementById('orderList');
async function loadOrders() {
statusMessage.textContent = 'Loading orders...';
orderList.textContent = '';
try {
const response = await fetch('/api/orders');
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const data = await response.json();
data.orders.forEach((order) => {
const item = document.createElement('li');
item.textContent = `${order.id} | ${order.customer} | ${order.status}`;
orderList.appendChild(item);
});
statusMessage.textContent = `Loaded ${data.orders.length} orders.`;
} catch (error) {
console.error('Order load failed:', error);
statusMessage.textContent = `Unable to load orders: ${error.message}`;
}
}
reloadButton.addEventListener('click', loadOrders);
loadOrders();
</script>
</body>
</html>
That is a clean diagnosis: you proved the 404 came from a route mismatch before changing one line. Next, you will investigate why a successful response still leaves the dashboard without order rows.
Fix the Response Contract
The route fix now gets the dashboard to the correct REST API endpoint. The page still cannot render the order list even though Google Chrome reports 200.
A successful transport response only confirms that the request reached its destination. The response body must still match the API contract that the frontend reads.
In this step, get ready to:
- Compare the successful response body against the frontend property.
- Record a root-cause hypothesis from the evidence.
- Align the server response with the existing frontend contract.
Compare the response with the frontend
A property name is part of a JSON response contract. The client must read the same property that the server sends.
- Switch back to the dashboard in Google Chrome.
- In the Chrome DevTools panel from earlier, select Network.
- Select the successful /api/orders request.
- Select Response.
- Identify items as the property containing the order records.
- Switch back to public/index.html in Visual Studio Code.
- Locate data.orders.forEach((order) => { inside loadOrders().
The server supplies items. The frontend reads data.orders.
- Return to Chrome DevTools.
- Select Console.
- Find the order-loading failure recorded after the successful request.
The Console confirms that rendering failed after the request completed. The Response evidence identifies the mismatched property as the cause.
What Does HTTP 200 Prove?
A 200 response confirms that the server successfully handled the HTTP request.
The frontend applies its own contract expectations after transport succeeds. Here, data.orders requires the response to contain an orders property.
State the root-cause hypothesis
An evidence-first troubleshooting hypothesis must explain both observations. The transport succeeds while the frontend property lookup fails.
Align the API response contract
JavaScript object shorthand lets { orders } create an orders property from the existing array. This gives the frontend the property it already expects.
- Switch back to index.js in Visual Studio Code.
- Locate the /api/orders route inside http.createServer().
- Find this line:
setTimeout(() => sendJson(response, 200, { items: orders }), 2000);
What Is Wrong With This Response?
The items key conflicts with the frontend's data.orders lookup. The array exists under a property the frontend never reads.
- Replace the mismatched response line with this version:
setTimeout(() => sendJson(response, 200, { orders }), 2000);
How Does This Fix the Contract?
The shorthand { orders } creates the property expected by data.orders. The route can now return the existing records under the agreed name.
- Save index.js.
The watch process in PowerShell restarts the server after the save.
Before you reload, what should change if the property name was the only correctness problem? The next check tests your prediction.
- Return to the dashboard in Google Chrome.
- Reload the page.
That is the contract fixed. After roughly two seconds, you'll see three rows for NW-1042, NW-1043, and NW-1044.
- Return to the Network panel.
- Select the latest /api/orders request.
- Select Response.
You'll see an orders array that matches data.orders in the frontend. The request still takes roughly two seconds because setTimeout still uses a delay of 2000 milliseconds.
Still Seeing the Contract Error?
- Confirm that the delayed callback in index.js contains { orders }.
- Confirm that public/index.html still reads data.orders.
- Select Reload orders on the dashboard after the server restarts.
Help me diagnose why my API returns 200 but the dashboard still cannot read data.orders.
✔️ Awesome, I've got everything!
Your index.js file now returns the order array under the correct property while keeping the planned delay.
ⓧ I'd like to double check the full code
const http = require('node:http');
const fs = require('node:fs');
const path = require('node:path');
const PORT = 3000;
const orders = [
{ id: 'NW-1042', customer: 'Amina', status: 'Processing' },
{ id: 'NW-1043', customer: 'Jordan', status: 'Packed' },
{ id: 'NW-1044', customer: 'Mei', status: 'Shipped' },
];
function sendJson(response, statusCode, payload) {
response.writeHead(statusCode, { 'Content-Type': 'application/json' });
response.end(JSON.stringify(payload));
}
const server = http.createServer((request, response) => {
console.log(`[request] ${request.method} ${request.url}`);
if (request.method === 'GET' && request.url === '/api/orders') {
setTimeout(() => sendJson(response, 200, { orders }), 2000);
return;
}
if (request.url.startsWith('/api/')) {
sendJson(response, 404, { error: 'Route not found' });
return;
}
if (request.method === 'GET' && (request.url === '/' || request.url === '/index.html')) {
const pagePath = path.join(__dirname, 'public', 'index.html');
fs.readFile(pagePath, 'utf8', (error, html) => {
if (error) { console.error('[incident] Unable to read the dashboard file:', error.message); sendJson(response, 500, { error: 'Unable to load the application' }); return; }
response.writeHead(200, { 'Content-Type': 'text/html' });
response.end(html);
});
return;
}
sendJson(response, 404, { error: 'Page not found' });
});
server.listen(PORT, () => { console.log(`Incident lab running at http://localhost:${PORT}`); });
What Should You Compare?
The /api/orders branch now sends { orders } inside the delayed callback. The setTimeout wrapper remains in place for the next investigation.
Your API contract and UI now agree. Next, you'll use request timing to remove the remaining delay before turning the evidence into a professional incident write-up.
Remove the API Delay and Write Your Post
Your response-contract fix now renders all three orders. A correct dashboard can still feel broken when its REST API is slow.
Request timing in Chrome DevTools can separate backend waiting from frontend rendering. That evidence gives your LinkedIn post a defensible troubleshooting story.
In this step, get ready to:
- Identify the slow server response with Chrome DevTools timing evidence.
- Remove the artificial delay while preserving the three order records.
- Write an evidence-first post that includes browser evidence plus Postman evidence.
Inspect the slow request timing
The dashboard now returns correct data after a repeatable wait. The Network timing breakdown shows which part of the request consumes that time.
- Switch back to the dashboard tab in Google Chrome.
- Return to the Network panel in DevTools.
- Select the Fetch/XHR filter.
Before you reload, predict whether most of the delay belongs to the server wait or browser rendering.
- Reload the dashboard at http://localhost:3000.
- Select the /api/orders request.
- Open the Timing tab.
You will see roughly two seconds in Waiting (TTFB). This phase includes the time the server spends preparing its response.
- Switch back to Visual Studio Code.
- Locate the /api/orders branch in index.js that uses setTimeout with a delay of 2000.
What does the timing prove?
The browser records the delay while it waits for the server. The matching setTimeout value connects that visible symptom to the backend code.
You now have evidence for the performance diagnosis before changing anything.
Remove the artificial delay
The timer delays an otherwise valid response. The route can send its JSON payload as soon as the request reaches the server.
- Select the entire line in index.js that contains setTimeout.
- Replace the selected line with the code below:
sendJson(response, 200, { orders });
What does this change do?
The route now sends the successful JSON response immediately. The payload still uses the orders property expected by the frontend.
- Save index.js.
The running watch process restarts the server after your saved change.
Before you reload, predict whether removing the timer changes the order data or only the wait.
- Switch back to the dashboard in Google Chrome.
- Reload the dashboard with the Network panel open.
- Select the new /api/orders request.
- Open the Timing tab.
You will see a much shorter Waiting (TTFB) phase. The dashboard still displays NW-1042, NW-1043, plus NW-1044.
That is the performance fix proven. The same three orders now arrive without the artificial two-second pause.
Still waiting roughly two seconds?
Confirm that the /api/orders branch no longer contains setTimeout. Save index.js again if the editor still shows an unsaved change.
Check the PowerShell terminal from earlier for a server restart before reloading the dashboard.
Help me diagnose why my local API still waits roughly two seconds.
✔️ Awesome, I've got everything!
Your index.js file now sends the orders response without the artificial delay.
ⓧ I'd like to double check the full code
const http = require('node:http');
const fs = require('node:fs');
const path = require('node:path');
const PORT = 3000;
const orders = [
{ id: 'NW-1042', customer: 'Amina', status: 'Processing' },
{ id: 'NW-1043', customer: 'Jordan', status: 'Packed' },
{ id: 'NW-1044', customer: 'Mei', status: 'Shipped' },
];
function sendJson(response, statusCode, payload) {
response.writeHead(statusCode, { 'Content-Type': 'application/json' });
response.end(JSON.stringify(payload));
}
const server = http.createServer((request, response) => {
console.log(`[request] ${request.method} ${request.url}`);
if (request.method === 'GET' && request.url === '/api/orders') {
sendJson(response, 200, { orders });
return;
}
if (request.url.startsWith('/api/')) {
sendJson(response, 404, { error: 'Route not found' });
return;
}
if (request.method === 'GET' && (request.url === '/' || request.url === '/index.html')) {
const pagePath = path.join(__dirname, 'public', 'index.html');
fs.readFile(pagePath, 'utf8', (error, html) => {
if (error) { console.error('[incident] Unable to read the dashboard file:', error.message); sendJson(response, 500, { error: 'Unable to load the application' }); return; }
response.writeHead(200, { 'Content-Type': 'text/html' });
response.end(html);
});
return;
}
sendJson(response, 404, { error: 'Page not found' });
});
server.listen(PORT, () => { console.log(`Incident lab running at http://localhost:${PORT}`); });
What should I compare?
Confirm that the /api/orders branch calls sendJson directly. The rest of the server keeps its existing routes plus error handling.
Write your evidence-first post
A strong troubleshooting post follows the investigation in the order it happened. Each claim points back to browser evidence, an independent API test, or a server observation.
- Select the api-incident-lab folder in the Visual Studio Code file list.
- Use the file creation control beside api-incident-lab to create linkedin-post.md.
You will see a blank linkedin-post.md editor tab inside the project folder.
- Add the evidence-first draft by pasting this content into linkedin-post.md.
# How I troubleshot three API failures without guessing
I built a small order dashboard with a local REST API, then treated its failures like a real support incident.
The first symptom was simple: the dashboard could not load any orders.
Instead of changing code immediately, I followed an evidence-first workflow:
1. Reproduce the issue consistently.
2. Inspect the browser request in Chrome DevTools.
3. Test the same endpoint independently in Postman.
4. Compare the API response with what the frontend expects.
5. Check request timing and server logs.
6. Apply one fix and verify the result before moving on.
That process uncovered three different root causes:
- A route mismatch produced a 404 response.
- A response returned 200 but used the wrong JSON property, so the UI still failed.
- A deliberate server delay made a correct response feel broken.
The biggest lesson was that an HTTP 200 only proves the request succeeded at the transport level. It does not prove the response matches the application's contract or meets its performance expectations.
Tools used: Chrome DevTools, Postman, Node.js, and Visual Studio Code.
My troubleshooting rule from now on: collect evidence, isolate the layer, change one thing, and verify.
#APIs #WebDevelopment #Troubleshooting #TechnicalSupport
How is the post structured?
- The symptom gives the investigation a clear starting point.
- The browser request plus Postman test isolate the route failure.
- The response comparison exposes the API contract mismatch.
- The timing evidence connects the slow request to the server delay.
- The final lesson turns the fixes into a reusable support method.
- Save linkedin-post.md.
- Personalize the post by confirming that its opening or closing sentence reflects your experience.
- Read the completed file from top to bottom.
You will see the symptom, evidence, root causes, fixes, verification, plus final lesson in one continuous account. The first-person opening makes the investigation ready to share.
Does the post feel incomplete?
Check that the draft names all three failures. These are the route mismatch, response-contract mismatch, plus artificial server delay.
Confirm that each diagnosis is followed by its fix plus a visible verification.
Help me review my API troubleshooting post for missing evidence.
✔️ Awesome, I've got everything!
Your linkedin-post.md file tells the complete evidence-first investigation in professional language.
ⓧ I'd like to double check the full code
# How I troubleshot three API failures without guessing
I built a small order dashboard with a local REST API, then treated its failures like a real support incident.
The first symptom was simple: the dashboard could not load any orders.
Instead of changing code immediately, I followed an evidence-first workflow:
1. Reproduce the issue consistently.
2. Inspect the browser request in Chrome DevTools.
3. Test the same endpoint independently in Postman.
4. Compare the API response with what the frontend expects.
5. Check request timing and server logs.
6. Apply one fix and verify the result before moving on.
That process uncovered three different root causes:
- A route mismatch produced a 404 response.
- A response returned 200 but used the wrong JSON property, so the UI still failed.
- A deliberate server delay made a correct response feel broken.
The biggest lesson was that an HTTP 200 only proves the request succeeded at the transport level. It does not prove the response matches the application's contract or meets its performance expectations.
Tools used: Chrome DevTools, Postman, Node.js, and Visual Studio Code.
My troubleshooting rule from now on: collect evidence, isolate the layer, change one thing, and verify.
#APIs #WebDevelopment #Troubleshooting #TechnicalSupport
What should I compare?
Confirm that your file follows the evidence-first sequence from symptom through lesson. Check that the three root causes match the incidents you reproduced in the lab.
Excellent work. Your dashboard now loads three orders quickly while your post explains every diagnosis with evidence.
Secret mission
Investigate a Safe 500 Incident
Add a controlled server failure to your order API. Reproduce the incident in Postman while keeping the internal cause inside the server log.
Clean Up Your Resources
Clean Up Your Resources
Choose whether to keep the lab available, pause its server, or delete its local files. Everything runs on your computer with no ongoing cost.
Resources you used:
- Your running Node.js watch process serving the dashboard at http://localhost:3000.
- Your local api-incident-lab folder containing the completed incident lab.
- Your locally stored Postman request history from the investigation.
Keep everything running
No action is needed. Choose this option if you want to keep testing the dashboard or extending its incidents.
- Keep the api-incident-lab folder available for future experiments.
- Leave the watch process running only while you are actively testing the API.
- Retain your local request history as evidence from the investigation.
- Keep the installed tools available for future troubleshooting labs.
Pause - I'll come back to this later
Stop the local server to free its terminal while preserving every project file. Your dashboard remains ready for the next investigation.
- Switch back to Visual Studio Code from earlier.
- Select the terminal running the Node.js watch process.
- Stop watch mode by running Terminal: Kill the Active Terminal Instance from the VS Code command list.
- Return to the dashboard tab from earlier.
- Refresh the page.
- Use the watch command from Step 2 when you are ready to resume the lab.
The dashboard no longer loads after the process stops. Your files remain inside api-incident-lab.
Delete - I don't want to use this again
Remove the local server, project folder, and request history when you no longer need the lab.
Save What You Want to Keep
Deleting the project folder also removes your repaired dashboard and LinkedIn draft. This local deletion cannot be reversed through the project.
- Save a copy of linkedin-post.md outside api-incident-lab if you want to keep your post.
- Switch back to Visual Studio Code from earlier.
- Select the terminal running the Node.js watch process.
- Stop watch mode by running Terminal: Kill the Active Terminal Instance from the VS Code command list.
- Select Terminal from the top menu.
- Select New Terminal to open a fresh PowerShell terminal.
- Move to the parent folder and remove api-incident-lab by running these PowerShell commands:
Set-Location ..
Remove-Item -Recurse -Force .\api-incident-lab
What Does This Cleanup Do?
- The first line moves PowerShell outside the project folder so the folder can be removed.
- The second line deletes the project folder with every file stored inside it.
PowerShell returns to its prompt without reporting an error. The api-incident-lab folder is now removed.
Folder Still Present?
Make sure the watch terminal is stopped before retrying the cleanup commands.
Help me diagnose why PowerShell cannot remove my local project folder.
- Return to Postman from earlier.
- Locate the requests to http://localhost:3000 in your local history.
- Remove those request entries using the local history controls.
- Confirm that the requests no longer appear in your local history.
That completes the local cleanup. Your installed tools stay ready for another lab.
Nice Work!
Nice Work!
You made it! Your repaired order dashboard now loads three orders quickly from a local REST API.
You've learned how to:
- Diagnose a 404 route mismatch with request evidence from Chrome DevTools. Confirm the root cause independently with Postman plus server logs.
- Repair a broken API contract so the dashboard renders all three orders from an HTTP 200 response.
- Use Timing plus Waiting (TTFB) to isolate a roughly two-second backend delay. Turn the investigation into a LinkedIn-ready post that follows the evidence-first sequence from symptom through verification.
- Secret Mission: Investigate a safe HTTP 500 incident with a generic client response. Preserve the detailed cause in server logs for support.
Ready to quiz yourself?