Skip to content

Question: How to implement smarter middleware type infer on context? #271

@HuakunShen

Description

@HuakunShen

I was using hono-openapi package for openapi, and want to migrate to chanafa, I like chanafa's way to define endpoints with class.
But one big problem I have with chanafa is type inference on context for middleware.

Example (with regular hono code)

export interface AccountContext {
	account: NonNullable<Awaited<ReturnType<typeof accountService.getAccount>>>
}


export const withAccountMiddleware = (): MiddlewareHandler<
	AppContextTypes & { Variables: AccountContext }
> => {
	return async (c, next) => {
		const account = await accountService.getAccount(c.req.valid("query").accountId)
		c.set("account", account!)
		await next()
	}
}

const app = new Hono()
app.get("/middleware-demo", withAccountMiddleware(), (c) => {
	const account = c.get("account")
	return c.json(account)
})

When withAccountMiddleware middleware is added to a route, its context types are automatically inherited.
TypeScript LSP knows c has account and c.get("account") type.
If I didn't add withAccountMiddleware(), then c.get("account") will show error warnings provided by typescript. So I can confidently use account directly without checking null or checking undefined.

But in Chanafa, routing and class endpoints are defined separately, middlewares are defined with routing.
The classes has no prior knowledge of the middlewares added before them.
I have to assume that account exists within a custom Context.

type AccountBindings = {
	Variables: {
		binanceAPI: BinanceAPI
		account: NonNullable<Awaited<ReturnType<typeof accountService.getAccount>>>
	}
}
type AppContextWithAccount = Context<AccountBindings>

class Endpint extends OpenAPIRoute {
        async handle(c: AppContextWithAccount) {
		const account = c.get("account")
                 ...
	}
}

If I forgot to add the middleware to inject account into context, there is no compile time warning. I will have to find out in runtime, with manual testing, unit testing or production environment error.
So I have to check whether account is null or undefined.

A slightly better solution:
The best workaround I found so far is to make an abstract class route like this, and call this.getAccount() instead of c.get("account"). So at least in runtime I get a proper error but not error with un-informative message like trying to access field "x" on undefined.

abstract class BinanceAccountRoute extends OpenAPIRoute {
	// Type-safe getter for account - will cause compile error if context doesn't have it
	protected getAccount(c: AppContextWithAccount) {
		const account = c.get("account")
		if (!account) {
			throw new HTTPException(500, { message: "Account not available in context" })
		}
		return account
	}

	// Override the handle method signature to require AppContextWithAccount
	abstract handle(c: AppContextWithAccount): Promise<any> | any
}

However this is still not ideal, the error could only be detected in runtime, not compile time.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions