Skip to content
On this page

Request Middleware

How it works

A Middleware in this context is a function, that gets some data in a request context, does what it needs to do and returns a given state.

You can think of them like Layers on an Onion - each Middleware is another Layer which you will only get to, if the previous one succeeded.

There are 3 types of Request-Middleware:

  • Request
  • Response
  • Exception

INFO

We need the additional name key on a Middleware object since minification will scramble the actual name of the Middleware.

Request Middleware

A Request Middleware can either check a given request for its options passed, or add data to the Request.

Examples:

  • AddAuthorizationHeader
  • AddBaseUrl
  • ResetValidationErrors
ts
import type { RequestMiddleware } from "@/toolkit"

export const addBaseUrl: RequestMiddleware = {
    name: "addBaseUrl",
    handle: async (options, config) => {
        options.baseURL ??= config.baseUrl

        return Promise.resolve(true)
    },
}
ts
import type { RequestMiddleware } from "@/toolkit"
import { useImpersonation } from "@/toolkit"

const { hasImpersonation, getImpersonation } = useImpersonation()

export const impersonateUser: RequestMiddleware = {
    name: "impersonateUser",
    handle: async (options) => {
        if (hasImpersonation()) {
            options.headers["x-impersonate"] = String(getImpersonation())
        }

        return Promise.resolve(true)
    },
}

Response Middleware

A Response Middleware gets the transformed response and can either reject (and create an exception) or resolve. It can also modify the response data down the chain.

Examples:

  • convertAllDatesToDayJsObjects
  • checkSchema
ts
import type { ResponseMiddleware } from "@/toolkit"

export const convertAllDatesToDayJsObjects: ResponseMiddleware = {
    name: "convertAllDatesToDayJsObjects",
    handle: async (response) => {
        const data = response.data as Record<string, any>

        const replaceDateRecursive = (container: Record<string, any>) => {
            for (const [key, value] of Object.entries(container)) {
                if (typeof value === "object" && value) {
                    replaceDateRecursive(value as Record<string, any>)
                    continue
                }

                if (typeof value === "string" && value.match(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z/gm)) {
                    container[key] = dayjs(value)
                }
            }
        }

        replaceDateRecursive(data)

        response.data = data as typeof response.data

        return Promise.resolve(response)
    },
}

WARNING

A response Middleware that resolves must return the response object.

Exception Middleware

An Exception Middleware gets the transformed response and can either reject (and create an exception) or resolve. It can also modify the response data down the chain.

Examples:

  • catchNetworkErrorException
  • catchValidationException
  • catchAuthorizationException
  • catchAuthenticationException
  • catchThrottlerException
ts
import type { ExceptionMiddleware } from "@/toolkit"

export const catchAuthorizationException: ExceptionMiddleware = {
    name: "catchAuthorizationException",
    handle: async (exception) => {
        if (exception.is(AuthorizationException)) {
            await goTo({ name: "login" })
        }
        
        return Promise.reject()
    },
}
ts
import type { ExceptionMiddleware } from "@/toolkit"

export const catchThrottlerException: ExceptionMiddleware = {
    name: "catchThrottlerException",
    handle: async (exception, validations, retry) => {
        if (exception.is(ThrottlerException)) {
            const retryAfter = exception.response.headers["retry-after"]
            const shouldRetry = retryAfter <= 5
            const currentTime = new Date().getTime()
            
            if(shouldRetry) {
                await until(currentTime + retryAfter >= currentTime)
                await retry()
            }
        }
    },
}
ts
import type { ExceptionMiddleware } from "@/toolkit"

export const catchValidationException: ExceptionMiddleware = {
    name: "catchValidationException",
    handle: async (exception, validations) => {
        if (exception.is(ValidationException)) {
            const { setErrors } = useFormState()

            setErrors(validations)
        }
    },
}

INFO

The Exception, even if caught by middleware, will still get propagated down the chain so you can still check them in your request try/catch blocks.

TIP

You can use the integrated .is() Method on an Error in a catch block to easily and declaratively check if it's a given exception.