Concepts (10-15 mins)
2. Middlewares

Action Middlewares

You can register a action middleware function by calling use method on your action router instance and providing a function that return promise of context.

Technically, All middleware types have same function signature and they all serves the same purpose. You can categorize a middleware based on where you are registering it in the chain/action routing tree.

Execution Order: Middlewares get executed in the order in which they are chained together.

📝 NOTE: For example project structure reference, click here

Middlewares can be categorized in three different levels:

  • Global level middlewares
  • Sub-Router level middlewares
  • Server action level middlewares

Let's explore each of them one by one!

Middleware function

As I mentioned it above, there's no technical difference between the middleware categories. Regardless, what you call it under the hood they have same signature, purpose and constraints.

Every middleware will receieve an object of type ActionRequest as an argument.

Type Definition:

import { cookies, headers } from "next/headers";
 
type ActionRequest<TContext> = {
  context: TContext;
  headers: ReturnType<typeof headers>;
  cookies: ReturnType<typeof cookies>;
};
 
type ActionMiddleware<TContext, TReturn> = (
  request: ActionRequest<TContext>
) => Promise<TReturn>;

Base context: { inputs: void } (Initial value)

Context: It's just a plain old javaScript object (aka POJO ). Intially having only "inputs" as a key. We'll talk about this in detail in upcoming pages.

⚠️ important: For now the only thing that you need to know is that always return a pojo from your middleware function. It's recommended that you just either return the same context as it is or extend it by adding few more properties on it before returning it. The returned value from a middleware will serve as new context for the next middleware in chain.

// --snip--
.use(async(request) => {
  console.log(request.context);
  console.log(request.cookies);
  console.log(request.headers);
  // if you haven't extended the original context
  // then just simply return the original one
  return request.context;
});

Middleware levels

In short, The middlewares registered at the very root of the router instance are called as global Middlewares and they will run for every single action request hitting the same router instance.

UseCase: Request logging, rate limiting, auth check, etc

// lib/action.router.ts
import { ActionRouter } from "next-action-router/server";
 
export const rootRouter = new ActionRouter({
  // router config
})
  .use(async ({ context }) => {
    console.log("Global middleware 1");
    return context;
  })
  .use(async ({ context }) => {
    console.log("Global middleware 2");
    return context;
  })
  .use(async ({ context }) => {
    console.log("Global middleware 3");
    return context;
  });

Middleware execution order:

Global middleware 1
Global middleware 2
Global middleware 3