Middleware

KambojaJS middleware concept is the same as Express middleware, except KambojaJS refine the usage of next function in Express middleware.

Instead of provide a next function, KambojaJS middleware use invocation object which is an object that will invoke appropriate controller.

import { Core } from "kamboja-express"

export class ExecutionTimeLogger extends Core.Middleware {
    async execute(context: Core.HttpRequest | Core.Handshake, invocation: Core.Invocation) {
        console.time("Execution Time")
        let result = await invocation.proceed()
        console.timeEnd("Execution Time")
        return result
    }
}

Above code is example of simple middleware, it will do noting except print the execution time of controller.

You almost can do anything and return any value inside execute method. You are free to modify the result of invocation and even you can choose to not to proceed the invocation and return an authentication error etc.

Invocation

Invocation as a short explanation is controller handled the current request. It has access to some information about current controller and its parameter values.

export abstract class Invocation {
    abstract proceed(): Promise<ActionResult>
    parameters: any[]
    controllerInfo?: ControllerInfo
    middlewares?: Middleware[]
}

You can access middlewares attached to current controller/request. This is necessary when you create a middleware that depends on other middleware.

proceed method will execute the controller and return a promised ActionResult, event in your controller you returned a value it will automatically wrapped with ActionResult

Note in some cases controllerInfo can be returned null, this is happened when a request doesn’t have associated controller.

Return ActionResult

In controller you are free to return anything, but in Middleware it is limited only to return ActionResult. See more detail explanation here

To take an advanced control to the http response you can return ActionResult derived class inside middleware.

import { Core, JsonActionResult } from "kamboja-express"

export class MyMiddleware extends Core.Middleware {
    async execute(context: Core.HttpRequest | Core.Handshake, invocation: Core.Invocation) {
        return new JsonActionResult({message: "Hello World!"})
    }
}

Action Scope Middleware

If applied in the action scope, middleware will only executed when the appropriate request related to the action.

import { Controller, middleware } from "kamboja-express"
import { ExecutionTimeLogger } from "../interceptor/execution-time"
 
export class ItemsController extends Controller {

    @middleware.use(new ExecutionTimeLogger())
    index() {
        return new ViewActionResult()
    }
}

Above code showing the ExecutionTimeLogger registered to the index action, its mean on every request of GET /items/index url, the ExecutionTimeLogger will be executed.

Controller Scope Middleware

If applied in the controller scope, the middleware will be executed on all requests associated to all controller actions.

import { Controller, middleware } from "kamboja-express"
import { ExecutionTimeLogger } from "../interceptor/execution-time"
 
@middleware.use(new ExecutionTimeLogger())
export class ItemsController extends Controller {
    index() {
        return new ViewActionResult()
    }
    about() {
        return new ViewActionResult()
    }
}

Above code showing the ExecutionTimeLogger applied on the controller, its mean the ExecutionTimeLogger will be executed on every request of GET /items/index and GET /items/about.

Global Scope Middleware

If applied in a global scope, middleware will executed in every request, including the request that doesn’t handled by a controller.

import { KambojaApplication, Core } from "kamboja-express"
import { ExecutionTimeLogger } from "../interceptor/execution-time"

let app = new KambojaApplication(__dirname)
    .use(new ExecutionTimeLogger())
    .init()
app.listen(5000)

Above code showing how to register a middleware in global scope. By using above setup, ExecutionTimeLogger middleware will be called on every request.

Middleware Order

Middleware executed in an order with priority. The execution process is started and chained from the highest priority and the last is executing the controller.

Below is the order of the middleware based on its priority.

  1. Action scope
  2. Controller scope
  3. Global scope

Highest priority means it will be executed first when a http request issued.

If an action/controller or global attached with multiple middleware, then the top most have the most priority. See example below about how the middleware will be executed.

//snippet from KambojaJS application
let app = new KambojaApplication(__dirname)
    .use(new Executed05())
    .use(new Executed06())
    .init()

//snippet from the controller 
@middleware.use(new Executed03())
@middleware.use(new Executed04())
export class ItemsController extends Controller {

    @middleware.use(new Executed01()) 
    @middleware.use(new Executed02())
    index(id:string) {
        return this.view()
    }
}

Above code showing separate snippet code of KambojaJS application and a controller attached with multiple interceptors. The interceptors will be executed in the following order:

  1. Executed01
  2. Executed02
  3. Executed03
  4. Executed04
  5. Executed05
  6. Executed06
  7. ItemsController.index

Middleware Id

In some cases you need to check wether an http request attached with other middlewares. For example you add an authentication middleware in a global scope, but you need to ignore some action. To do that you only need to create another empty middleware with an ID then check on the authentication logic if the specific request contains the middleware with the appropriate id.

import { Core, middleware } from "kamboja-express"

@middleware.id("your-app:authorize")
export class Authorize extends Core.Middleware {
    execute(context: Core.HttpRequest | Core.Handshake, invocation: Core.Invocation) {
        return invocation.proceed()
    }
}

Above code is a middleware to identify a specific action/controller is authorized to continue the request.

import { Core, Interceptor } from "kamboja-express"

@interceptor.id("your-app:authorize")
export class AuthenticateInterceptor extends Core.Middleware {
    async execute(context: Core.HttpRequest | Core.Handshake, invocation: Core.Invocation) {
        if(invocation.middlewares.some(x => Interceptor.getId(x) == "your-app:authorize"))
            return invocation.proceed()
        else {
            //process authentication here
            //if authentic execute invocation.proceed()
            //if not authentic return 401 or 403 or RedirectActionResult
        }
    }
}