Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for defining custom context helper methods #3965

Open
cheese-mountain opened this issue Feb 28, 2025 · 3 comments
Open

Support for defining custom context helper methods #3965

cheese-mountain opened this issue Feb 28, 2025 · 3 comments
Labels
enhancement New feature or request.

Comments

@cheese-mountain
Copy link

What is the feature you are proposing?

What is the feature you are proposing?

I'm proposing the ability to define and register custom helper methods that will be directly accessible on the Context object. This would enable developers to create reusable utility functions that have access to the context instance through this binding, improving code organization and developer experience.

Currently, utility functions need to be imported separately and explicitly passed the context, or set as variables on the context. Having first-class support for helper methods would make the code more elegant and maintainable.

Why is this feature valuable?

  • Improves code organization by allowing related functionality to be grouped as context methods
  • Reduces boilerplate by eliminating the need to repeatedly pass context to utility functions
  • Provides better TypeScript integration with proper this typing
  • Makes middleware and route handlers cleaner and more readable
  • Follows patterns common in other frameworks where context/request objects can be extended

Current Implementation vs Proposed Feature

Current Implementation:

type Env = {
    Bindings: {
        SUPABASE_URL: string;
        SUPABASE_ANON_KEY: string;
    }, 
    Variables: {
        'supabase-token': string
        supabase: TypedSupabaseClient
        log: (str: unknown) => void
    }
}

const factory = createFactory<Env>()
export const Router = factory.createApp
export const createMiddleware = factory.createMiddleware

export type Context = HonoContext<Env>

// In route handlers:
app.get('/users', async (c) => {
  console.log(`${c.req.method} ${c.req.path}: Fetching users`); // Commonly replaced with a log helper method
  
  const supabase = c.get('supabase');
  const { data, error } = await supabase.from('users').select('*'); // Also could be replaced with helper method
  
  if (error) {
    console.log(`${c.req.method} ${c.req.path}: ${error}`); // Same here
    return c.json({ error: 'Failed to fetch users' }, 500);
  }
  
  return c.json(data);
})

Proposed Feature Implementation:

const helpers = {
    log: function(this: Context, message: string, level: 'info'| 'error' = 'info') {
        console[level](`[${this.req.method}] ${this.req.path}: ${message}`);
    }, 
    useSupabase: async function(this: Context, callback: (supabase: TypedSupabaseClient) => Promise<any>){
        const supabase = this.get('supabase')
        const { data, error } = await callback(supabase)
        if (error) {
            this.log(error)
            return null
        }
        return data
    }
}
type Env = { ..., Helpers: typeof helpers }
const factory = createFactory<Env>({ defaultAppOptions: { helpers } })

// In route handlers:
app.get('/users', async (c) => {
  c.log('Fetching users');
  const users = await c.useSupabase(client => client.from('users').select('*'));
  return users ? c.json(users) : c.json({ error: 'Failed to fetch users' }, 500);
})

Possible Implementation

Working possible implementation (which I'm using myself)

// Types.ts, extend Env with helpers
export type Bindings = object
export type Variables = object
export type Helpers = Record<string, any>

export type BlankEnv = {}
export type Env = {
  Bindings?: Bindings
  Variables?: Variables
  Helpers?: Helpers
}

// hono-base.ts, extend HonoOptionswith helpers
export type HonoOptions<E extends Env> = {
 ...
helpers?: E['Helpers']
}

// Context.ts
// Rename class & assign helpers in constructor if provided
export class ContextBase<
  E extends Env = any,
  P extends string = any,
  I extends Input = {}
>  {  
 ...
constructor(req: Request, options?: ContextOptions<E>) {
    this.#rawRequest = req
    if (options) {
      this.#executionCtx = options.executionCtx
      this.env = options.env
      this.#notFoundHandler = options.notFoundHandler
      this.#path = options.path
      this.#matchResult = options.matchResult

      if (options.helpers) {
        Object.assign(this, options.helpers)
      }
    }
  }
...
}

// Extend ContextBase with Context to typesafely include helper methods
type HelperMethods<E extends Env = Env> = 
  E['Helpers'] extends undefined ? {} :
  IsAny<E['Helpers']> extends true ? {} :
  E['Helpers'] extends Helpers ? NonNullable<E['Helpers']> : {}

export type Context<
  E extends Env = any,
  P extends string = any,
  I extends Input = {},
> = HelperMethods<E> & ContextBase<E, P, I>

export const Context = ContextBase as (
  new <E extends Env = any, P extends string = any, I extends Input = {}>
    (...args: ConstructorParameters<typeof ContextBase<E, P, I>>) => Context<E, P, I>
)
@cheese-mountain cheese-mountain added the enhancement New feature or request. label Feb 28, 2025
@yusukebe
Copy link
Member

yusukebe commented Mar 2, 2025

Related to #414

@cheese-mountain
Copy link
Author

Related to #414

Hello!

I saw that issue and considered it a lack of implementation. What do you think of the propsed solution?

I personally think it allows for a cleaner architecture and enjoy using it in my own hono apps. If you agree I can make a pr to discuss this further.

@yusukebe
Copy link
Member

yusukebe commented Mar 9, 2025

@cheese-mountain

We've done the discussion in #414. I don't want to add more implementation to the hono for the feature you suggest.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request.
Projects
None yet
Development

No branches or pull requests

2 participants