how can i chain multiple middlewares to one route?

716 Views Asked by At

I want to chain 2 middlewares functions in deno oak like this:

const addProductPanier = async(req:any,res:any) => {...}
const showPanier = async(ctx:any) => {...}

router.post('/OBV/panier',addProductPanier).post('/OBV/panier',showPanier);

I've tried in so many ways and searched for a solution in the oak documentation, but string paths in .post can not be the same, I need to have for example:

router.post('/OBV/panier',addProductPanier).post('/OBV/panier/one',showPanier);

I also tried to merge the 2 middlewares in one, it worked few days ago, but for some unknown reason it don't work anymore replying me that response.render() is not a function. As you can see I separated both of them to have addProductPanier sending data to my database and showPanier to fetch this data and display it on my html page using ctx.render().

So do you know how to chain multiples middlewares to one route ?

1

There are 1 best solutions below

4
jsejcksn On

Answer summary

You can use Oak's middleware composition function (composeMiddlware) or you can simply provide each middleware function as a positional argument to the router method.


Guided explanation

Because there's not a minimal, reproducible example in your question, I'll provide one below in the form of a simple greeting app and use it to address your question, detailing two ways to compose middleware on the same route.

Useful reference documentation:

Example app description

Let's say we want to create a web server that should respond to GET requests at /greet, and also allow an optional route parameter name for the name to greet, so the route will look like this: /greet/:name?. When that route is matched, the server should use individual middleware to:

  • log the value of the name route parameter (in the server console), and then
  • respond with a plaintext greeting message.

The middleware functions described above might look like this:

./middleware.ts:

import { type RouterMiddleware } from "https://deno.land/x/[email protected]/mod.ts";

export const logName: RouterMiddleware<"/greet/:name?"> = (ctx, next) => {
  const { name } = ctx.params;
  console.log({ name });
  return next();
};

export const sendGreeting: RouterMiddleware<"/greet/:name?"> = (ctx, next) => {
  const name = ctx.params.name ?? "World";
  const msg = `Hello ${name}!`;
  ctx.response.body = msg;
  ctx.response.type = "text/plain";
  return next();
};

Now let's create a module where the routes will be defined. For now, we'll just initialize a router and export it so that there aren't type errors as we continue setup, but we'll come back here to explore the two composition methods:

./routes.ts:

import { Router } from "https://deno.land/x/[email protected]/mod.ts";

export const router = new Router();

Let's also create a module where we initialize and export the app (and a function for printing a startup message to the console when the server starts):

./app.ts:

import { Application } from "https://deno.land/x/[email protected]/mod.ts";
import { router } from "./routes.ts";

// This is not necessary, but is potentially helpful to see in the console
function printStartupMessage({ hostname, port, secure }: {
  hostname: string;
  port: number;
  secure?: boolean;
}): void {
  const address = new URL(
    `http${secure ? "s" : ""}://${
      hostname === "0.0.0.0" ? "localhost" : hostname
    }:${port}/`,
  ).href;
  console.log(`Listening at ${address}`);
  console.log("Use ctrl+c to stop");
}

export const app = new Application();
app.addEventListener("listen", printStartupMessage);

app.use(router.routes());
app.use(router.allowedMethods());

For the last part of the setup, we'll create the main app entrypoint module where the server is started:

./main.ts:

import { app } from "./app.ts";

await app.listen({ port: 8000 });

Now, let's return to ./routes.ts to explore the composition methods:

Composing middleware functions

The first way that middleware can be composed is to use a function exported by Oak for exactly this purpose: composeMiddlware

The modified version of our routes module would look like this:

./routes.ts:

import {
  composeMiddleware,
  Router,
} from "https://deno.land/x/[email protected]/mod.ts";

import { logName, sendGreeting } from "./middleware.ts";

export const router = new Router();
const greetMiddleware = composeMiddleware([logName, sendGreeting]);
router.get("/greet/:name?", greetMiddleware);

Or, more simply, each middleware function can just be supplied as a positional argument to the router method in order:

./routes.ts:

import { Router } from "https://deno.land/x/[email protected]/mod.ts";

import { logName, sendGreeting } from "./middleware.ts";

export const router = new Router();
router.get("/greet/:name?", logName, sendGreeting);

Both of these produce the same result.

Testing the app

Start the app in the terminal console with the appropriate permissions for network access:

% deno run --allow-net=0.0.0.0:8000 main.ts
Listening at http://localhost:8000/
Use ctrl+c to stop

If you navigate to http://localhost:8000/greet in your browser, you should see the text Hello World! in the viewport, and back in the terminal console a line with { name: undefined }.

Again, if you navigate to http://localhost:8000/greet/visitor, you should see the text Hello visitor! in the viewport, and back in the terminal console a line with { name: "visitor" }.