Skip to content

Instantly share code, notes, and snippets.

@patrickalima98
Last active January 11, 2024 02:51
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save patrickalima98/4ae1e90c8b96e56f3747b9bbe677d51d to your computer and use it in GitHub Desktop.
Save patrickalima98/4ae1e90c8b96e56f3747b9bbe677d51d to your computer and use it in GitHub Desktop.
Lazy load engine for Deno and Hono

Hono Lazy loader

This is a simple and rudmentar prototyping of a lazy loader for Hono to be used with Deno and Deno Deploy. Maybe can be adapated for others runtimes.

Why?

Pushing the limits of what is possible with Deno Deploy, I developed a moderately small Api service (44 files, 10 endpoints and 3 handlers). The project supports Postgres with Drizzle, called postgres and redis. However, as the project scaled and more libraries were added I began to experience very long cold starts, an inconsistent average of (4 - 15sec).

After many tests I verified that there was a bottleneck in loading the files (handlers), deno deploy started taking a long time to load the files, when eliminating the imports the problem disappeared, in essence Hono does not support Lazy Loading, this gist provides a prototype for that. When initializing, a standard and generic server will be loaded, only importing when the current route handler is requested.

Fee fre to use or adapat for your needs :)

How to use?

// Imports
import { Hono } from "https://deno.land/x/hono@v3.9.2/preset/quick.ts";
import { registerDefaultRouter, registerLazyRouters, lazyRouter } from 'https://gist.githubusercontent.com/patrickalima98/4ae1e90c8b96e56f3747b9bbe677d51d/raw/1df12b42a85a0c85445db50a17b72ecf1f5c05b9/hono-lazy-load.ts';

// Declare here your handlers to lazy load
const lazyRoutes = {
  user: (async () => (await import('./handlers/user/index.ts'))),
  health: (async () => (await import('./handlers/health/index.ts'))),
} as const;

// This is an example of a custom function you can pass to
// registerDefaultRouter and lazyRouter functions to do sometind customized like apply a middleware
// to all handlers or to the default handler
// This is an optinal function, you can ignore this and using registerDefaultRouter and lazyRouter
// without it.
function main(newApp: Hono<any, {}, "/">) {
  const app = newApp || new Hono()

  // Hono App
  app.use('*', cors({ credentials: true, origin: 'http://localhost:3000' }))

  app.onError((err: any, c): any => {
    if (err.message === 'Validation failure') {
      return c.json({ errors: err.messages }, err.status)
    }

    if (err instanceof InternalValidation) {
      return c.json({ errors: [err.error] }, err.status)
    }

    console.error(err)
  })

  return app;
}


// You need to register all the handlers. This will be saved in the globalThis.cacheRoutes and globalThis.lazyRouters.
registerLazyRouters(lazyRoutes)
registerDefaultRouter<Hono, any>(router());

// Initialize Deno and serve importing the lazyRouter
Deno.serve((req, evt) => lazyRouter(req, evt, router))
import { Hono } from "https://deno.land/x/hono@v3.9.2/preset/quick.ts";
// Types
export type TLazyRouters = Record<string, () => Promise<any>>
export type TCachedRoutes = Record<string, any>;
declare global {
var cacheRoutes: TCachedRoutes;
var lazyRouters: TLazyRouters;
}
export function registerDefaultRouter<T, Z>(defaultRouter: T | Hono<Z | any, {}, "/">) {
globalThis.cacheRoutes = {
defaultRouter
};
}
export function registerLazyRouters<T extends {[key in string]: () => Promise<any>}>(
routers: T): void {
globalThis.lazyRouters = routers
};
function createEmptyInstance(path: string, router: any, callback?: (app: Hono<any, {}, "/">) => void) {
const app = new Hono();
app.route(`/${path}`, router);
if (callback) {
callback(app);
}
return app;
}
export async function lazyRouter(req: Request, evt: Deno.ServeHandlerInfo, callback: (app: Hono<any, {}, "/">) => void) {
const url = new URL(req.url);
const [, domainRouter] = url.pathname.split('/');
if (!(domainRouter in cacheRoutes) && domainRouter in globalThis.lazyRouters) {
const loadedRouter = (await globalThis.lazyRouters[domainRouter as keyof typeof globalThis.lazyRouters]()).default;
const routerInstance = createEmptyInstance(domainRouter, loadedRouter, callback);
globalThis.cacheRoutes[domainRouter] = routerInstance;
return await routerInstance.fetch(req, evt)
}
return (cacheRoutes[domainRouter] || cacheRoutes.defaultRouter).fetch(req, evt)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment