Bird
Raised Fist0
Denoframework~15 mins

Middleware patterns in Deno - Deep Dive

Choose your learning style10 modes available
Overview - Middleware patterns
What is it?
Middleware patterns are ways to organize code that runs between a request and a response in web servers or applications. They let you add features like logging, authentication, or error handling in small, reusable pieces. Each middleware piece can do something and then pass control to the next one. This helps keep code clean and easy to manage.
Why it matters
Without middleware patterns, all code for handling requests would be mixed together, making it hard to read, fix, or add new features. Middleware lets developers add or change parts of the request process without touching the whole system. This saves time and reduces bugs, making web apps more reliable and easier to grow.
Where it fits
Before learning middleware patterns, you should understand basic web servers and how requests and responses work. After mastering middleware, you can learn about routing, error handling, and advanced server frameworks that build on middleware concepts.
Mental Model
Core Idea
Middleware patterns are like a chain of helpers that each do a small job on a request before passing it along.
Think of it like...
Imagine a package moving through a series of workers on an assembly line. Each worker adds something or checks the package before sending it to the next. Middleware works the same way with requests.
Request --> [Middleware 1] --> [Middleware 2] --> [Middleware 3] --> Response
Each middleware can modify the request or response and decides when to pass control forward.
Build-Up - 7 Steps
1
FoundationUnderstanding basic request-response flow
šŸ¤”
Concept: Learn how a web server receives a request and sends back a response.
When a user visits a website, their browser sends a request to the server. The server processes this request and sends back a response, like a webpage or data. This simple flow is the foundation for middleware.
Result
You see how requests and responses travel between client and server.
Understanding this flow is key because middleware sits right in the middle, handling requests before responses go back.
2
FoundationWhat is middleware in web servers
šŸ¤”
Concept: Middleware is code that runs between receiving a request and sending a response.
Middleware functions get the request and response objects and can do things like log info, check user login, or change data. After finishing, they call the next middleware or end the response.
Result
You know middleware acts as a middle step in request handling.
Knowing middleware is a middle step helps you see how to break big tasks into smaller, manageable parts.
3
IntermediateChaining middleware functions
šŸ¤”Before reading on: do you think middleware runs all at once or one after another? Commit to your answer.
Concept: Middleware functions are arranged in a chain, each calling the next when done.
In Deno, middleware functions receive a context and a next function. Calling next() passes control to the next middleware. This creates a chain where each middleware can do work before and after the next one runs.
Result
Middleware runs in order, allowing layered processing of requests.
Understanding chaining explains how middleware can build on each other's work and control flow.
4
IntermediateCommon middleware tasks and examples
šŸ¤”Before reading on: which tasks do you think middleware can handle? Logging, authentication, or rendering pages? Commit to your answer.
Concept: Middleware is used for tasks like logging, authentication, parsing data, and error handling.
Examples include logging request details, checking if a user is logged in, parsing JSON bodies, or catching errors to send friendly messages. Each task is a separate middleware function.
Result
You see how middleware divides responsibilities cleanly.
Knowing common tasks helps you design middleware that is focused and reusable.
5
IntermediateMiddleware in Deno with async/await
šŸ¤”
Concept: Middleware functions in Deno use async/await to handle asynchronous operations smoothly.
Because requests often involve waiting (like reading data), middleware functions are async. They await next() to pause until the next middleware finishes, then continue. This lets middleware run code before and after the next step.
Result
Middleware can handle async tasks without blocking the server.
Understanding async middleware prevents bugs where code runs too early or too late.
6
AdvancedError handling and middleware order importance
šŸ¤”Before reading on: do you think middleware order affects error handling? Commit to yes or no.
Concept: The order of middleware affects how errors are caught and handled.
If error-handling middleware runs too late, errors may not be caught. Middleware earlier in the chain can catch errors thrown by later middleware. Proper order ensures errors are handled gracefully and responses are sent correctly.
Result
You learn to arrange middleware to catch and handle errors effectively.
Knowing order importance helps avoid silent failures and improves app reliability.
7
ExpertMiddleware internals and control flow in Deno
šŸ¤”Before reading on: do you think next() returns a promise that middleware can await? Commit to yes or no.
Concept: Middleware chaining in Deno relies on promises and async functions to control flow precisely.
Each middleware calls next(), which returns a promise representing the next middleware's completion. Awaiting next() pauses the current middleware until the next finishes. This allows middleware to run code both before and after downstream middleware, enabling patterns like timing or cleanup.
Result
You understand how middleware controls flow and timing internally.
Understanding this promise-based flow unlocks advanced middleware patterns and debugging techniques.
Under the Hood
Middleware in Deno is implemented as async functions that receive a context object and a next function. Calling next() triggers the next middleware in the chain and returns a promise. Awaiting next() pauses the current middleware until the next completes, allowing code before and after next() to run. This creates a stack-like flow where middleware can wrap around others, enabling pre- and post-processing of requests and responses.
Why designed this way?
This design uses async/await and promises to handle asynchronous operations naturally, avoiding callback hell. It allows middleware to be composed flexibly and run in order, with clear control over when to pass control forward or handle errors. Alternatives like synchronous middleware or callbacks were less flexible and harder to manage, especially with asynchronous tasks common in web servers.
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ Incoming Req  │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
       │
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā–¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ Middleware 1 │
│  (before)   │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
       │ next()
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā–¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ Middleware 2 │
│  (before)   │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
       │ next()
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā–¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ Middleware 3 │
│  (before)   │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
       │ next()
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā–¼ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│   Handler    │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
       │ response
       ā–²
       │ (after)
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ Middleware 3 │
│  (after)    │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
       ā–²
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ Middleware 2 │
│  (after)    │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
       ā–²
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│ Middleware 1 │
│  (after)    │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”¬ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
       ā–²
ā”Œā”€ā”€ā”€ā”€ā”€ā”€ā”“ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”
│  Response    │
ā””ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”€ā”˜
Myth Busters - 4 Common Misconceptions
Quick: Does middleware always run in parallel? Commit to yes or no.
Common Belief:Middleware functions run at the same time, so order doesn't matter.
Tap to reveal reality
Reality:Middleware runs one after another in a chain, and order is very important.
Why it matters:Ignoring order can cause bugs where some middleware never runs or errors are not caught.
Quick: Can middleware skip calling next() and still send a response? Commit to yes or no.
Common Belief:Middleware must always call next() to continue the chain.
Tap to reveal reality
Reality:Middleware can choose not to call next() and end the response early, for example, to block unauthorized users.
Why it matters:Not knowing this can cause confusion when some middleware stops the chain intentionally.
Quick: Is middleware only for modifying requests? Commit to yes or no.
Common Belief:Middleware only changes incoming requests before the main handler.
Tap to reveal reality
Reality:Middleware can also modify responses after the main handler runs, by running code after awaiting next().
Why it matters:Missing this limits middleware use cases like adding headers or logging response times.
Quick: Does middleware always handle errors automatically? Commit to yes or no.
Common Belief:Middleware automatically catches and handles all errors in the chain.
Tap to reveal reality
Reality:Only middleware designed for error handling catches errors; others must pass errors along.
Why it matters:Assuming automatic error handling can lead to uncaught errors and crashes.
Expert Zone
1
Middleware can run code both before and after calling next(), enabling patterns like timing how long downstream middleware takes.
2
Stacking middleware in the wrong order can cause subtle bugs, especially with authentication and error handling.
3
Middleware functions share the same context object, so modifying it affects all downstream middleware and handlers.
When NOT to use
Middleware patterns are not ideal for very simple applications where adding layers adds unnecessary complexity. For performance-critical paths, direct handler code may be faster. Alternatives include event-driven or reactive programming models that avoid chaining.
Production Patterns
In production Deno apps, middleware is used for logging requests, parsing JSON bodies, authenticating users, handling CORS, and catching errors globally. Middleware is often organized in separate files and composed in a main app file for clarity and reuse.
Connections
Chain of Responsibility pattern
Middleware is a practical example of the Chain of Responsibility design pattern.
Understanding this pattern helps grasp how middleware passes control along a chain until one handles the request.
Unix pipes and filters
Middleware chaining is similar to Unix pipes where output of one command feeds into the next.
This connection shows how data flows through stages, each transforming or acting on it.
Assembly line manufacturing
Middleware resembles an assembly line where each worker adds value before passing the product on.
Seeing middleware as an assembly line clarifies why order and small focused steps matter.
Common Pitfalls
#1Middleware functions forget to call next(), causing requests to hang.
Wrong approach:async function logger(ctx, next) { console.log('Request received'); // missing await next() }
Correct approach:async function logger(ctx, next) { console.log('Request received'); await next(); }
Root cause:Not understanding that calling next() passes control forward and without it, the chain stops.
#2Placing error-handling middleware after response is sent, so errors are not caught.
Wrong approach:app.use(handler); app.use(errorHandler); // runs too late
Correct approach:app.use(errorHandler); app.use(handler); // error handler runs first
Root cause:Misunderstanding middleware order and its effect on error catching.
#3Modifying shared context in one middleware without care, causing unexpected side effects.
Wrong approach:async function modify(ctx, next) { ctx.user = null; // overwrites user info await next(); }
Correct approach:async function modify(ctx, next) { if (!ctx.user) ctx.user = {}; await next(); }
Root cause:Not realizing that context is shared and changes affect all downstream middleware.
Key Takeaways
Middleware patterns organize request handling into a chain of small, focused functions.
Each middleware can run code before and after the next middleware, controlling flow with async/await.
Order of middleware matters greatly for tasks like authentication and error handling.
Middleware can end the request early or pass it along, giving flexible control over processing.
Understanding middleware internals helps write better, more reliable web applications in Deno.

Practice

(1/5)
1. What is the main purpose of middleware in a Deno web application?
easy
A. To compile TypeScript code into JavaScript
B. To store data permanently in a database
C. To run code step-by-step for each incoming request
D. To create user interface components

Solution

  1. Step 1: Understand middleware role

    Middleware processes each request in order, allowing code to run before sending a response.
  2. Step 2: Compare options

    Only To run code step-by-step for each incoming request describes this step-by-step request handling. Other options describe unrelated tasks.
  3. Final Answer:

    To run code step-by-step for each incoming request -> Option C
  4. Quick Check:

    Middleware = step-by-step request code [OK]
Hint: Middleware runs code for each request in order [OK]
Common Mistakes:
  • Confusing middleware with database operations
  • Thinking middleware builds UI components
  • Assuming middleware compiles code
2. Which of the following is the correct way to call the next middleware in Deno?
easy
A. next.await()
B. next()
C. call next()
D. await next()

Solution

  1. Step 1: Recall async middleware pattern

    Middleware functions are async and must await next() to continue the chain.
  2. Step 2: Check syntax correctness

    Only 'await next()' correctly waits for the next middleware to finish before continuing.
  3. Final Answer:

    await next() -> Option D
  4. Quick Check:

    Use await next() to proceed [OK]
Hint: Always use await before next() in async middleware [OK]
Common Mistakes:
  • Calling next() without await causing unexpected behavior
  • Using incorrect syntax like call next()
  • Trying to use next.await() which is invalid
3. Given this middleware code snippet in Deno:
async function logger(ctx, next) {
  console.log('Before');
  await next();
  console.log('After');
}

async function handler(ctx) {
  ctx.response.body = 'Hello';
}

// Middleware chain: logger -> handler
What will be printed to the console when a request is handled?
medium
A. Before\nAfter
B. After\nBefore
C. Before
D. After

Solution

  1. Step 1: Analyze middleware flow

    Logger prints 'Before', then awaits next middleware (handler), then prints 'After'.
  2. Step 2: Understand order of prints

    Since handler runs between the two console.logs, output order is 'Before' then 'After'.
  3. Final Answer:

    Before\nAfter -> Option A
  4. Quick Check:

    Middleware logs before and after next() [OK]
Hint: Middleware logs before and after await next() [OK]
Common Mistakes:
  • Assuming 'After' prints before 'Before'
  • Thinking only 'Before' or 'After' prints
  • Ignoring await causing wrong order
4. Identify the error in this Deno middleware code:
async function auth(ctx, next) {
  if (!ctx.request.headers.get('Authorization')) {
    ctx.response.status = 401;
    ctx.response.body = 'Unauthorized';
  }
  await next();
}
medium
A. Missing await before ctx.request.headers.get
B. Should return after setting 401 to stop middleware chain
C. ctx.response.body must be a JSON object
D. next() should not be awaited

Solution

  1. Step 1: Understand middleware flow control

    If authorization header is missing, response is set to 401, but code still calls await next(), continuing the chain.
  2. Step 2: Identify fix to stop chain

    To prevent further middleware running, function should return immediately after setting 401 response.
  3. Final Answer:

    Should return after setting 401 to stop middleware chain -> Option B
  4. Quick Check:

    Return early to stop middleware chain [OK]
Hint: Return after response to stop middleware chain [OK]
Common Mistakes:
  • Not returning causes next middleware to run anyway
  • Thinking await is missing on header get
  • Believing response body must be JSON always
5. You want to create a middleware in Deno that measures how long each request takes and adds a custom header X-Response-Time with the duration in milliseconds. Which of the following middleware implementations correctly achieves this?
hard
A. async function responseTime(ctx, next) { const start = performance.now(); await next(); const ms = performance.now() - start; ctx.response.headers.set('X-Response-Time', ms.toFixed(2)); }
B. async function responseTime(ctx, next) { const start = Date.now(); const ms = Date.now() - start; ctx.response.headers.set('X-Response-Time', `${ms}ms`); await next(); }
C. async function responseTime(ctx, next) { const start = Date.now(); next(); const ms = Date.now() - start; ctx.response.headers.set('X-Response-Time', `${ms}ms`); }
D. async function responseTime(ctx, next) { await next(); const ms = Date.now(); ctx.response.headers.set('X-Response-Time', ms); }

Solution

  1. Step 1: Measure time before and after next()

    Start timer before await next(), then calculate duration after it finishes.
  2. Step 2: Use precise timer and set header correctly

    performance.now() gives better precision than Date.now(). Setting header with ms.toFixed(2) formats nicely.
  3. Final Answer:

    Middleware using performance.now(), await next(), and setting header after next() -> Option A
  4. Quick Check:

    Use performance.now() and await next() then set header [OK]
Hint: Start timer before await next(), set header after [OK]
Common Mistakes:
  • Calculating time before await next() runs
  • Using Date.now() which is less precise
  • Setting header before next() completes
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard