π Middleware & the Request-Response Cycle
With routing basics out of the way, it's time to understand how Express actually works under the hood. Middleware and the request-response cycle aren't just another topic β they're the core mental model behind everything else in Express.
π₯ The Big Picture

A request hits the server.
Express creates a request object and a response object.
That data needs to be processed to generate a meaningful response.
Express processes it using middleware.
π§© What Is Middleware?
A middleware is simply a function that sits between the request coming in and the response going out. It can:
Modify the request object
Modify the response object
Run any other logic
You've actually already used middleware without calling it that β express.json() (aka body-parser) is a classic example; it's what gives you access to the request body. Logging, setting headers, authentication β all of it is middleware. The possibilities are endless.
β Key insight: Even route handlers are middleware β just ones that only run for specific routes.
π The Middleware Stack
All of an app's middleware functions, taken together, form the middleware stack.
β οΈ Critical rule: Middleware runs in the exact order it's defined in your code. Whatever's defined first, runs first. This makes code order genuinely important in Express.

π How a Request Flows Through the Stack
Think of it as a pipeline:
The request/response objects are created.
They pass through each middleware function one at a time, in definition order.
Every middleware gets access to three things:
req,res, andnext.Calling
next()hands off the samereq/resobjects to the next middleware in line.This repeats until the stack is exhausted.
The last middleware is typically the route handler β and it's different: instead of calling next(), it sends the response and ends the cycle.
<div class="infographic-mwpipe" id="mwpipe">
<style>
.infographic-mwpipe {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--display-font: 'Fraunces', Georgia, serif;
--body-font: 'Inter', system-ui, sans-serif;
--mono-font: 'IBM Plex Mono', monospace;
max-width: 880px;
margin: 0 auto;
box-sizing: border-box;
padding: 0 clamp(1.25rem, 5vw, 3rem);
font-family: var(--body-font);
color: var(--ink);
background: transparent;
}
.infographic-mwpipe[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
}
.infographic-mwpipe *,
.infographic-mwpipe *::before,
.infographic-mwpipe *::after { box-sizing: border-box; }
.mw-head {
display: flex; align-items: center; justify-content: space-between;
gap: 1rem; margin: 2rem 0 2rem;
}
.mw-title {
font-family: var(--display-font); font-weight: 600;
font-size: clamp(1.4rem, 3.2vw, 1.9rem); margin: 0; letter-spacing: -0.01em;
}
.mw-play {
display: inline-flex; align-items: center; gap: 0.5rem;
background: var(--paper); color: var(--ink); border: 1px solid var(--line);
font: inherit; font-size: 0.85rem; font-weight: 600; cursor: pointer;
padding: 0.55rem 1rem; border-radius: 999px; transition: border-color .15s, color .15s;
flex: 0 0 auto;
}
.mw-play:hover { border-color: var(--accent); color: var(--accent); }
.mw-play svg { width: 15px; height: 15px; display: block; }
.mw-play.is-playing { border-color: var(--accent); color: var(--accent); }
.mw-row {
display: flex; align-items: center; flex-wrap: wrap;
gap: 0.4rem 0; margin-bottom: 2.5rem;
}
.mw-node {
flex: 1 1 128px;
min-width: 108px;
background: var(--paper-alt);
border: 1px solid var(--line);
border-radius: 10px;
padding: 1.1rem 0.75rem;
text-align: center;
transition: border-color .25s, box-shadow .25s, transform .25s;
}
.mw-node .mw-icon {
width: 26px; height: 26px; margin: 0 auto 0.6rem;
color: var(--ink-soft); transition: color .25s;
}
.mw-node .mw-icon svg { width: 100%; height: 100%; display: block; }
.mw-node .mw-label {
font-size: 0.82rem; font-weight: 600; line-height: 1.25;
white-space: nowrap;
}
.mw-node .mw-code {
font-family: var(--mono-font); font-size: 0.68rem; color: var(--ink-soft);
margin-top: 0.35rem; white-space: nowrap;
}
.mw-node.is-active {
border-color: var(--accent);
box-shadow: 0 0 0 3px var(--accent-soft);
transform: translateY(-2px);
}
.mw-node.is-active .mw-icon { color: var(--accent); }
.mw-node.is-active .mw-code { color: var(--accent); }
.mw-console {
background: var(--paper-alt); border: 1px solid var(--line); border-radius: 10px;
padding: 1rem 1.25rem; margin: 0 0 1.75rem;
display: flex; align-items: baseline; gap: 0.6rem; flex-wrap: wrap;
font-family: var(--mono-font); font-size: 0.95rem;
}
.mw-console .mw-console-key { color: var(--ink-soft); }
.mw-console .mw-console-val {
color: var(--accent); font-weight: 600; transition: opacity .15s;
}
.mw-console .mw-console-val a { color: var(--accent); text-decoration: underline; }
.mw-console .mw-console-val .mw-sent {
font-family: var(--body-font); font-weight: 600; font-size: 0.78rem;
color: var(--ink-soft); margin-left: 0.2rem;
}
.mw-console .mw-console-val.is-updating { opacity: 0; }
.mw-console .mw-console-val.mw-pulse { animation: mw-pulse 0.35s ease; }
@keyframes mw-pulse {
from { opacity: 0; transform: translateY(-3px); }
to { opacity: 1; transform: translateY(0); }
}
.mw-arrow {
flex: 0 0 auto;
width: clamp(34px, 6vw, 56px);
display: flex; flex-direction: column; align-items: center; justify-content: center;
color: var(--line);
position: relative;
}
.mw-arrow svg { width: 100%; height: 20px; display: block; overflow: visible; }
.mw-arrow .mw-arrow-tag {
position: absolute; top: -1.15rem; left: 50%; transform: translateX(-50%);
font-family: var(--mono-font); font-size: 0.62rem; color: var(--ink-soft);
white-space: nowrap; letter-spacing: 0.01em;
}
.mw-arrow .mw-path { stroke: var(--line); stroke-width: 2; fill: none; transition: stroke .2s; }
.mw-arrow.is-dashed .mw-path { stroke-dasharray: 4 4; }
.mw-arrow .mw-head { fill: var(--line); transition: fill .2s; }
.mw-arrow .mw-dot { fill: var(--accent); opacity: 0; }
.mw-arrow.is-flowing .mw-path,
.mw-arrow.is-flowing .mw-head { stroke: var(--accent); fill: var(--accent); }
.mw-arrow.is-flowing .mw-dot {
opacity: 1;
animation: mw-travel 0.55s ease-in-out forwards;
}
@keyframes mw-travel {
from { transform: translateX(0); opacity: 1; }
85% { opacity: 1; }
to { transform: translateX(34px); opacity: 0; }
}
@media (max-width: 620px) {
.mw-row { flex-direction: column; align-items: stretch; }
.mw-node { width: 100%; }
.mw-arrow { width: 100%; height: 30px; transform: rotate(90deg); margin: 0.35rem 0; }
.mw-arrow .mw-arrow-tag { transform: rotate(-90deg) translateX(-50%); top: 3px; left: -0.6rem; }
}
@media (prefers-reduced-motion: reduce) {
.mw-node, .mw-arrow .mw-path, .mw-arrow .mw-head { transition: none; }
.mw-arrow.is-flowing .mw-dot { animation: none; opacity: 1; }
.mw-console .mw-console-val.mw-pulse { animation: none; }
}
</style>
<div class="mw-head">
<h2 class="mw-title">Middleware Pipeline</h2>
<button class="mw-play" type="button" id="mwPlayBtn">
<span class="mw-play-icon"></span>
<span class="mw-play-label">Play</span>
</button>
</div>
<div class="mw-console">
<span class="mw-console-key">res β</span>
<span class="mw-console-val" id="mwConsoleVal">"codeveda"</span>
</div>
<div class="mw-row" id="mwRow">
<div class="mw-node" data-node="0">
<div class="mw-icon" data-icon="download"></div>
<div class="mw-label">Request</div>
<div class="mw-code">res = "codeveda"</div>
</div>
<div class="mw-arrow" data-arrow="0">
<svg viewBox="0 0 40 20"><path class="mw-path" d="M2 10h30"/><path class="mw-head" d="M28 5l8 5-8 5z"/><circle class="mw-dot" cx="4" cy="10" r="2.5"/></svg>
</div>
<div class="mw-node" data-node="1">
<div class="mw-icon" data-icon="filter"></div>
<div class="mw-label">Middleware 1</div>
<div class="mw-code">res.toUpperCase()</div>
</div>
<div class="mw-arrow" data-arrow="1">
<span class="mw-arrow-tag">next()</span>
<svg viewBox="0 0 40 20"><path class="mw-path" d="M2 10h30"/><path class="mw-head" d="M28 5l8 5-8 5z"/><circle class="mw-dot" cx="4" cy="10" r="2.5"/></svg>
</div>
<div class="mw-node" data-node="2">
<div class="mw-icon" data-icon="filter"></div>
<div class="mw-label">Middleware 2</div>
<div class="mw-code">res.link(url)</div>
</div>
<div class="mw-arrow is-dashed" data-arrow="2">
<span class="mw-arrow-tag">β― next()</span>
<svg viewBox="0 0 40 20"><path class="mw-path" d="M2 10h30"/><path class="mw-head" d="M28 5l8 5-8 5z"/><circle class="mw-dot" cx="4" cy="10" r="2.5"/></svg>
</div>
<div class="mw-node" data-node="3">
<div class="mw-icon" data-icon="flag"></div>
<div class="mw-label">Route Handler</div>
<div class="mw-code">return res</div>
</div>
<div class="mw-arrow" data-arrow="3">
<span class="mw-arrow-tag">send</span>
<svg viewBox="0 0 40 20"><path class="mw-path" d="M2 10h30"/><path class="mw-head" d="M28 5l8 5-8 5z"/><circle class="mw-dot" cx="4" cy="10" r="2.5"/></svg>
</div>
<div class="mw-node" data-node="4">
<div class="mw-icon" data-icon="upload"></div>
<div class="mw-label">Response</div>
<div class="mw-code">res.send()</div>
</div>
</div>
<script>
(function () {
var wrapper = document.currentScript.closest('.infographic-mwpipe')
|| document.querySelector('.infographic-mwpipe');
var bodyAttr = document.body.getAttribute('data-darkmode');
var dark;
if (bodyAttr === 'true') { dark = true; }
else if (bodyAttr === 'false') { dark = false; }
else {
var match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
var ICONS = {
download: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 15V3"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="m7 10 5 5 5-5"/></svg>',
filter: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M10 20a1 1 0 0 0 .553.895l2 1A1 1 0 0 0 14 21v-7a2 2 0 0 1 .517-1.341L21.74 4.67A1 1 0 0 0 21 3H3a1 1 0 0 0-.742 1.67l7.225 7.989A2 2 0 0 1 10 14z"/></svg>',
flag: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M4 22V4a1 1 0 0 1 .4-.8A6 6 0 0 1 8 2c3 0 5 2 7.333 2q2 0 3.067-.8A1 1 0 0 1 20 4v10a1 1 0 0 1-.4.8A6 6 0 0 1 16 16c-3 0-5-2-8-2a6 6 0 0 0-4 1.528"/></svg>',
upload: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 3v12"/><path d="m17 8-5-5-5 5"/><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/></svg>',
play: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M5 5a2 2 0 0 1 3.008-1.728l11.997 6.998a2 2 0 0 1 .003 3.458l-12 7A2 2 0 0 1 5 19z"/></svg>'
};
wrapper.querySelectorAll('[data-icon]').forEach(function (el) {
el.innerHTML = ICONS[el.getAttribute('data-icon')];
});
var playBtn = wrapper.querySelector('#mwPlayBtn');
playBtn.querySelector('.mw-play-icon').innerHTML = ICONS.play;
var nodes = Array.prototype.slice.call(wrapper.querySelectorAll('[data-node]'));
var arrows = Array.prototype.slice.call(wrapper.querySelectorAll('[data-arrow]'));
var valueEl = wrapper.querySelector('#mwConsoleVal');
var timers = [];
var VALUES = [
'"codeveda"',
'"CODEVEDA"',
'<a href="https://www.codeveda.dev">CODEVEDA</a>',
'<a href="https://www.codeveda.dev">CODEVEDA</a>',
'<a href="https://www.codeveda.dev" target="_blank" rel="noopener">CODEVEDA</a> <span class="mw-sent">β sent</span>'
];
function clearTimers() { timers.forEach(clearTimeout); timers = []; }
function resetAll() {
nodes.forEach(function (n) { n.classList.remove('is-active'); });
arrows.forEach(function (a) {
a.classList.remove('is-flowing');
var dot = a.querySelector('.mw-dot');
dot.style.animation = 'none';
void dot.offsetWidth;
dot.style.animation = '';
});
valueEl.classList.remove('mw-pulse');
valueEl.innerHTML = VALUES[0];
}
function setValue(i) {
valueEl.innerHTML = VALUES[i];
valueEl.classList.remove('mw-pulse');
void valueEl.offsetWidth;
valueEl.classList.add('mw-pulse');
}
function play() {
clearTimers();
resetAll();
playBtn.classList.add('is-playing');
playBtn.querySelector('.mw-play-label').textContent = 'Replay';
var step = 0;
var HOLD = 480, FLOW = 550;
nodes.forEach(function (node, i) {
timers.push(setTimeout((function (node, i) {
return function () { node.classList.add('is-active'); setValue(i); };
})(node, i), step));
step += HOLD;
if (arrows[i]) {
timers.push(setTimeout((function (arrow) {
return function () { arrow.classList.add('is-flowing'); };
})(arrows[i]), step));
step += FLOW;
}
});
timers.push(setTimeout(function () { playBtn.classList.remove('is-playing'); }, step));
}
playBtn.addEventListener('click', play);
})();
</script>
</div>That entire flow β request in, through every middleware, response out β is the request-response cycle.
β Key Takeaway
Middleware = any function with access to
req,res, andnext, running between request and response.Execution order = code order. Always.
next()passes the samereq/resforward to the next function in the stack.Route handlers are just the final middleware β they send the response instead of calling
next().This pipeline mental model is what makes
express.json()(and Express generally) finally click.