π¦ Sending Structured Data (HTML & JSON)
Beyond plain text, you can send back HTML or JSON β the browser renders each one differently based on the Content-Type header you set.
π HTML Response
// index.js
import { createServer } from 'node:http';
const server = createServer((req, res) => {
res.setHeader('Content-Type', 'text/html');
res.end('<h1>Hello from HTML!</h1><p>This is a web page</p>');
});
server.listen(3000, () => {
console.log('HTML Server running at <http://localhost:3000>');
});Content-Type: text/htmltells the browser to render the string as HTML, rather than showing the raw tags as plain text.res.end(html)can take the content directly, so a separateres.write()call isn't needed here.
π» Terminal output:
HTML Server running at <http://localhost:3000>π Browser output β visiting http://localhost:3000 shows a rendered page:
Hello from HTML!
This is a web page
(The raw HTML sent was <h1>Hello from HTML!</h1><p>This is a web page</p> β the browser turns those tags into an actual heading and paragraph.)
π§Ύ JSON Response
// index.js
import { createServer } from 'node:http';
const server = createServer((req, res) => {
res.setHeader('Content-Type', 'application/json');
const data = {
name: 'Varsha',
course: 'Node.js',
mood: 'Awesome'
};
res.end(JSON.stringify(data));
});
server.listen(3000, () => {
console.log('JSON Server running at <http://localhost:3000>');
});Content-Type: application/jsontells the browser (and any code reading the response) that the body is JSON, not plain text or HTML.JSON.stringify(data)converts the JS object into a JSON-formatted string βres.end()can only send strings or buffers, not raw objects.
π» Terminal output:
JSON Server running at <http://localhost:3000>π Browser output β visiting http://localhost:3000 shows the raw JSON:
{"name":"Varsha","course":"Node.js","mood":"Awesome"}π‘ Make It Pretty
Browsers show JSON as one unformatted line by default. A JSON formatter extension displays it with indentation and syntax highlighting instead.
π§ Recommended extensions:
π’ Chrome β JSON Formatter by Callum Locke
π£ Firefox β JSON Lite
π Edge β JSON Viewer
With one installed, the same response renders like this instead:
{
"name": "Varsha",
"course": "Node.js",
"mood": "Awesome"
}β¨ Quick Reference
π Plain text β
Content-Type: text/plainπ HTML β
Content-Type: text/htmlπ§Ύ JSON β
Content-Type: application/json
res.setHeader('Content-Type', 'text/html');
res.end('<h1>Hello from HTML!</h1><p>This is a web page</p>');<div class="infographic-httptypes">
<style>
.infographic-httptypes {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--ok: #1f9d55;
--ok-soft: #e5f6ec;
--radius: 10px;
box-sizing: border-box;
max-width: 880px;
margin: 0 auto;
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
color: var(--ink);
background: transparent;
}
.infographic-httptypes[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #13171F;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
--ok: #34d399;
--ok-soft: #163227;
}
.infographic-httptypes * { box-sizing: border-box; }
.infographic-httptypes .t-inner { padding: 0 clamp(1rem, 4vw, 1.5rem); }
.infographic-httptypes .t-eyebrow {
font-size: 0.7rem; font-weight: 600; letter-spacing: 0.09em;
text-transform: uppercase; color: var(--accent); margin: 0 0 0.35rem;
}
.infographic-httptypes h2 {
font-family: 'Newsreader', Georgia, serif; font-weight: 600;
font-size: clamp(1.5rem, 4vw, 2rem); line-height: 1.15;
margin: 0 0 0.3rem; letter-spacing: -0.01em;
}
.infographic-httptypes .t-sub { font-size: 0.9rem; color: var(--ink-soft); margin: 0 0 0.7rem; }
.infographic-httptypes .t-boot {
display: inline-block; font-family: 'SFMono-Regular', Consolas, Menlo, monospace; font-size: 0.72rem;
color: var(--ink-soft); background: var(--paper-alt); border: 1px solid var(--line);
border-radius: 999px; padding: 0.28rem 0.75rem; margin-bottom: 1.2rem;
}
.infographic-httptypes .t-boot .t-boot-ok { color: var(--ok); }
/* type pills */
.infographic-httptypes .t-pills { display: flex; gap: 0.45rem; flex-wrap: wrap; margin-bottom: 0.9rem; }
.infographic-httptypes .t-pill {
font-family: 'SFMono-Regular', Consolas, Menlo, monospace; font-size: 0.78rem; font-weight: 600;
color: var(--ink-soft); background: var(--paper); border: 1px solid var(--line);
border-radius: 999px; padding: 0.4rem 0.85rem; cursor: pointer; transition: all 0.15s ease;
display: inline-flex; align-items: center; gap: 0.3rem;
}
.infographic-httptypes .t-pill:hover { border-color: var(--accent); }
.infographic-httptypes .t-pill.is-on { background: var(--accent); border-color: var(--accent); color: #fff; }
/* editable content row */
.infographic-httptypes .t-editrow { margin-bottom: 1rem; min-height: 40px; }
.infographic-httptypes .t-edit-label {
font-size: 0.68rem; font-weight: 600; letter-spacing: 0.05em; text-transform: uppercase;
color: var(--ink-soft); margin: 0 0 0.3rem; display: block;
}
.infographic-httptypes .t-input {
width: 100%; font-family: 'SFMono-Regular', Consolas, Menlo, monospace; font-size: 0.87rem;
padding: 0.48rem 0.65rem; border: 1px solid var(--line); border-radius: 8px;
background: var(--paper); color: var(--ink);
}
.infographic-httptypes .t-input:focus { outline: 2px solid var(--accent); outline-offset: 1px; }
.infographic-httptypes .t-json-fields { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.infographic-httptypes .t-json-field { flex: 1; min-width: 110px; }
.infographic-httptypes .t-json-field .t-input { font-size: 0.82rem; padding: 0.4rem 0.55rem; }
/* scene */
.infographic-httptypes .t-scene-card {
border: 1px solid var(--line); border-radius: var(--radius); background: var(--paper-alt);
padding: 1.1rem 1.1rem 1.2rem; margin-bottom: 1rem;
}
.infographic-httptypes .t-scene { display: grid; grid-template-columns: 1.4fr 84px 0.6fr; align-items: center; gap: 0.4rem; margin-bottom: 1rem; }
@media (max-width: 560px) { .infographic-httptypes .t-scene { grid-template-columns: 1fr; } }
.infographic-httptypes .t-browser { border: 1px solid var(--line); border-radius: 9px; background: var(--paper); overflow: hidden; }
.infographic-httptypes .t-browser-bar { display: flex; align-items: center; justify-content: space-between; gap: 0.4rem; padding: 0.4rem 0.55rem; background: var(--paper-alt); border-bottom: 1px solid var(--line); }
.infographic-httptypes .t-browser-bar-left { display: flex; align-items: center; gap: 0.4rem; }
.infographic-httptypes .t-browser-bar svg { width: 12px; height: 12px; stroke: var(--ink-soft); flex: none; }
.infographic-httptypes .t-browser-url { font-family: 'SFMono-Regular', Consolas, Menlo, monospace; font-size: 0.7rem; color: var(--ink-soft); }
.infographic-httptypes .t-view-toggle {
font-family: 'SFMono-Regular', Consolas, Menlo, monospace; font-size: 0.62rem; font-weight: 700;
color: var(--ink-soft); background: var(--paper); border: 1px solid var(--line); border-radius: 999px;
padding: 0.15rem 0.5rem; cursor: pointer; display: none;
}
.infographic-httptypes .t-view-toggle:hover { border-color: var(--accent); color: var(--ink); }
.infographic-httptypes .t-view-toggle.is-shown { display: inline-block; }
.infographic-httptypes .t-browser-body { min-height: 92px; padding: 0.9rem; display: flex; align-items: center; justify-content: center; text-align: center; }
.infographic-httptypes .t-render-html h1 { font-size: 1.15rem; margin: 0 0 0.25rem; font-family: 'Newsreader', Georgia, serif; }
.infographic-httptypes .t-render-html p { font-size: 0.82rem; margin: 0; color: var(--ink-soft); }
.infographic-httptypes .t-render-plain { font-family: 'SFMono-Regular', Consolas, Menlo, monospace; font-size: 0.88rem; color: var(--ink); }
.infographic-httptypes .t-render-json { font-family: 'SFMono-Regular', Consolas, Menlo, monospace; font-size: 0.82rem; color: var(--ink); white-space: pre-wrap; text-align: left; width: 100%; }
.infographic-httptypes .t-json-key { color: var(--accent); }
.infographic-httptypes .t-json-str { color: var(--ok); }
.infographic-httptypes .t-placeholder { color: var(--ink-soft); font-family: 'SFMono-Regular', Consolas, Menlo, monospace; font-size: 0.85rem; }
.infographic-httptypes .t-actor { display: flex; flex-direction: column; align-items: center; gap: 0.3rem; }
.infographic-httptypes .t-actor-icon {
width: 38px; height: 38px; border-radius: 10px; background: var(--paper); border: 1px solid var(--line);
display: flex; align-items: center; justify-content: center;
box-shadow: 0 1px 3px rgba(0,0,0,0.10), 0 1px 1px rgba(0,0,0,0.06);
transition: transform 0.2s ease, border-color 0.2s ease;
}
.infographic-httptypes[data-theme="dark"] .t-actor-icon { box-shadow: 0 1px 4px rgba(0,0,0,0.45); }
.infographic-httptypes .t-actor-icon svg { width: 19px; height: 19px; stroke: var(--accent); }
.infographic-httptypes .t-actor-icon.is-busy { transform: scale(1.12); border-color: var(--accent); }
.infographic-httptypes .t-lane { display: flex; flex-direction: column; align-items: center; gap: 0.35rem; }
.infographic-httptypes .t-lane-track { width: 100%; height: 3px; background: var(--line); border-radius: 2px; position: relative; margin: 3px 0; }
.infographic-httptypes .t-dot {
position: absolute; top: 50%; transform: translate(-50%, -50%); left: 0;
padding: 0.13rem 0.4rem; border-radius: 999px; font-size: 0.58rem; font-weight: 700;
font-family: 'SFMono-Regular', Consolas, Menlo, monospace; white-space: nowrap;
opacity: 0; background: var(--accent); color: #fff; transition: left 0.55s ease, opacity 0.2s ease;
}
.infographic-httptypes .t-dot.is-go { opacity: 1; }
.infographic-httptypes .t-dot.at-end { left: 100%; }
.infographic-httptypes .t-dot.is-response { background: var(--ok); }
.infographic-httptypes .t-server-box { display: flex; align-items: center; justify-content: center; padding: 0.8rem; border: 1px solid var(--line); border-radius: 9px; background: var(--paper); }
.infographic-httptypes .t-header-tag {
font-family: 'SFMono-Regular', Consolas, Menlo, monospace; font-size: 0.72rem; color: var(--ink-soft);
text-align: center;
}
.infographic-httptypes .t-header-tag b { color: var(--accent); }
.infographic-httptypes .t-pretty-row { display: flex; justify-content: center; margin-top: 0.6rem; }
.infographic-httptypes .t-pretty-btn {
font-family: 'SFMono-Regular', Consolas, Menlo, monospace; font-size: 0.7rem; font-weight: 700;
color: var(--ink-soft); background: var(--paper); border: 1px solid var(--line); border-radius: 999px;
padding: 0.25rem 0.7rem; cursor: pointer; display: none;
}
.infographic-httptypes .t-pretty-btn.is-shown { display: inline-block; }
.infographic-httptypes .t-pretty-btn:hover { border-color: var(--accent); color: var(--ink); }
.infographic-httptypes .t-pretty-hint { font-size: 0.66rem; color: var(--ink-soft); margin-left: 0.4rem; font-style: italic; }
</style>
<div class="t-inner">
<span class="t-boot">$ node index.js β <span class="t-boot-ok">running at localhost:3000</span></span>
<div class="t-pills" id="tPills">
<button class="t-pill" data-type="plain" type="button">π text/plain</button>
<button class="t-pill" data-type="html" type="button">π text/html</button>
<button class="t-pill" data-type="json" type="button">π§Ύ application/json</button>
</div>
<div class="t-editrow" id="tEditRow"></div>
<div class="t-scene-card">
<div class="t-scene">
<div class="t-browser">
<div class="t-browser-bar">
<div class="t-browser-bar-left">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
<span class="t-browser-url">localhost:3000</span>
</div>
<button class="t-view-toggle" id="tViewToggle" type="button">view source</button>
</div>
<div class="t-browser-body" id="tBrowserBody"><span class="t-placeholder">pick a type & visit</span></div>
</div>
<div class="t-lane">
<div class="t-actor"><span class="t-actor-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg></span></div>
<div class="t-lane-track"><span class="t-dot" id="tDotReq">GET</span></div>
<div class="t-lane-track"><span class="t-dot is-response" id="tDotRes">200</span></div>
<div class="t-actor"><span class="t-actor-icon" id="tServerIcon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="8" x="2" y="2" rx="2" ry="2"/><rect width="20" height="8" x="2" y="14" rx="2" ry="2"/><line x1="6" x2="6.01" y1="6" y2="6"/><line x1="6" x2="6.01" y1="18" y2="18"/></svg></span></div>
</div>
<div class="t-server-box">
<span class="t-header-tag" id="tHeaderTag">Content-Type:<br><b>β</b></span>
</div>
</div>
<div class="t-pretty-row">
<button class="t-pretty-btn" id="tPrettyBtn" type="button">π§ pretty-print</button>
<span class="t-pretty-hint" id="tPrettyHint"></span>
</div>
</div>
</div>
<script>
(function () {
var wrapper = document.currentScript.closest('.infographic-httptypes')
|| document.querySelector('.infographic-httptypes');
var bodyAttr = document.body.getAttribute('data-darkmode');
var dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
var m = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = m ? m[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
var reduced = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
var pills = Array.prototype.slice.call(wrapper.querySelectorAll('#tPills .t-pill'));
var editRow = wrapper.querySelector('#tEditRow');
var browserBody = wrapper.querySelector('#tBrowserBody');
var viewToggle = wrapper.querySelector('#tViewToggle');
var dotReq = wrapper.querySelector('#tDotReq');
var dotRes = wrapper.querySelector('#tDotRes');
var serverIcon = wrapper.querySelector('#tServerIcon');
var headerTag = wrapper.querySelector('#tHeaderTag');
var prettyBtn = wrapper.querySelector('#tPrettyBtn');
var prettyHint = wrapper.querySelector('#tPrettyHint');
var state = {
type: null,
plainText: 'Hello, World!',
htmlText: '<h1>Hello from HTML!</h1><p>This is a web page</p>',
json: { name: 'Varsha', course: 'Node.js', mood: 'Awesome' },
showingSource: false,
pretty: false
};
var timers = [];
var busy = false;
function clearTimers() { timers.forEach(clearTimeout); timers = []; }
function esc(s) { return s.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>'); }
function renderEditRow() {
if (state.type === 'plain') {
editRow.innerHTML =
'<label class="t-edit-label">res.end(...)</label>' +
'<input class="t-input" id="tPlainInput" type="text" value="' + esc(state.plainText) + '" spellcheck="false" />';
wrapper.querySelector('#tPlainInput').addEventListener('input', function (e) { state.plainText = e.target.value; });
} else if (state.type === 'html') {
editRow.innerHTML =
'<label class="t-edit-label">res.end(...)</label>' +
'<input class="t-input" id="tHtmlInput" type="text" value="' + esc(state.htmlText) + '" spellcheck="false" />';
wrapper.querySelector('#tHtmlInput').addEventListener('input', function (e) { state.htmlText = e.target.value; });
} else if (state.type === 'json') {
editRow.innerHTML =
'<label class="t-edit-label">const data = { ... }</label>' +
'<div class="t-json-fields">' +
'<div class="t-json-field"><input class="t-input" id="tJName" type="text" value="' + esc(state.json.name) + '" /></div>' +
'<div class="t-json-field"><input class="t-input" id="tJCourse" type="text" value="' + esc(state.json.course) + '" /></div>' +
'<div class="t-json-field"><input class="t-input" id="tJMood" type="text" value="' + esc(state.json.mood) + '" /></div>' +
'</div>';
wrapper.querySelector('#tJName').addEventListener('input', function (e) { state.json.name = e.target.value; });
wrapper.querySelector('#tJCourse').addEventListener('input', function (e) { state.json.course = e.target.value; });
wrapper.querySelector('#tJMood').addEventListener('input', function (e) { state.json.mood = e.target.value; });
} else {
editRow.innerHTML = '';
}
}
function jsonPretty(obj) {
var json = JSON.stringify(obj, null, 2);
return esc(json)
.replace(/"([^"]+)":/g, '<span class="t-json-key">"$1"</span>:')
.replace(/: "([^"]*)"/g, ': <span class="t-json-str">"$1"</span>');
}
function jsonRaw(obj) {
var json = JSON.stringify(obj);
return esc(json)
.replace(/"([^"]+)":/g, '<span class="t-json-key">"$1"</span>:')
.replace(/: "([^"]*)"/g, ': <span class="t-json-str">"$1"</span>');
}
function renderResult() {
viewToggle.classList.remove('is-shown');
prettyBtn.classList.remove('is-shown');
prettyHint.textContent = '';
if (state.type === 'plain') {
browserBody.innerHTML = '<span class="t-render-plain">' + esc(state.plainText) + '</span>';
} else if (state.type === 'html') {
viewToggle.classList.add('is-shown');
viewToggle.textContent = state.showingSource ? 'rendered' : 'view source';
if (state.showingSource) {
browserBody.innerHTML = '<span class="t-render-plain">' + esc(state.htmlText) + '</span>';
} else {
browserBody.innerHTML = '<div class="t-render-html">' + state.htmlText + '</div>';
}
} else if (state.type === 'json') {
prettyBtn.classList.add('is-shown');
prettyBtn.textContent = state.pretty ? 'β© raw' : 'π§ pretty-print';
prettyHint.textContent = state.pretty ? '(with a formatter extension)' : '(default β one line)';
browserBody.innerHTML = '<span class="t-render-json">' + (state.pretty ? jsonPretty(state.json) : jsonRaw(state.json)) + '</span>';
}
}
viewToggle.addEventListener('click', function () {
state.showingSource = !state.showingSource;
renderResult();
});
prettyBtn.addEventListener('click', function () {
state.pretty = !state.pretty;
renderResult();
});
function visit() {
if (busy || !state.type) return;
busy = true;
clearTimers();
var contentType = state.type === 'plain' ? 'text/plain' : state.type === 'html' ? 'text/html' : 'application/json';
headerTag.innerHTML = 'Content-Type:<br><b>' + contentType + '</b>';
browserBody.innerHTML = '<span class="t-placeholder">loadingβ¦</span>';
viewToggle.classList.remove('is-shown');
prettyBtn.classList.remove('is-shown');
prettyHint.textContent = '';
dotReq.classList.remove('is-go', 'at-end');
dotRes.classList.remove('is-go', 'at-end');
var t = reduced ? 0 : 40;
timers.push(setTimeout(function () {
dotReq.classList.add('is-go');
timers.push(setTimeout(function () { dotReq.classList.add('at-end'); }, 20));
}, t));
timers.push(setTimeout(function () { serverIcon.classList.add('is-busy'); }, t + (reduced ? 0 : 550)));
timers.push(setTimeout(function () {
serverIcon.classList.remove('is-busy');
dotReq.classList.remove('is-go', 'at-end');
dotRes.classList.add('is-go');
timers.push(setTimeout(function () { dotRes.classList.add('at-end'); }, 20));
}, t + (reduced ? 0 : 1000)));
timers.push(setTimeout(function () {
dotRes.classList.remove('is-go', 'at-end');
renderResult();
busy = false;
}, t + (reduced ? 0 : 1600)));
}
pills.forEach(function (pill) {
pill.addEventListener('click', function () {
pills.forEach(function (p) { p.classList.remove('is-on'); });
pill.classList.add('is-on');
state.type = pill.getAttribute('data-type');
state.showingSource = false;
state.pretty = false;
renderEditRow();
visit();
});
});
})();
</script>
</div>