Skip to content

Each source folder serve a specific concern - marketing site, customer app, admin, etc.

Yet, development workflow is identical.

πŸš€ Starting the Dev Server ​

sh
pnpm dev          # all source folders
pnpm dev front    # specific folder (front, admin, app, etc.)

Default port is 4556, configured as devPort in package.json.

πŸ”€ What Happens on Start ​

  1. Vite compiles api/app.ts
  2. Dev server starts, serving both client pages and your API routes
  3. Requests are routed between Vite and your API
  4. File watcher monitors API files for changes

βš™οΈ api/dev.ts ​

api/dev.ts exposes three hooks for customizing the dev experience.

requestHandler ​

Returns the API request handler. Generated default:

ts
import { devSetup } from "_/api:factory";
import app from "./app";

export default devSetup({
  requestHandler() {
    return app.callback();
  },
});
ts
import { getRequestListener } from "@hono/node-server";
import { devSetup } from "_/api:factory";
import app from "./app";

export default devSetup({
  requestHandler() {
    return getRequestListener(app.fetch);
  },
});

Override this for custom routing logic - WebSocket handling, multi-handler dispatch, etc.

requestMatcher ​

Controls which requests go to your API vs Vite. Defaults to matching apiurl prefix:

ts
export default devSetup({
  requestHandler() { return app.callback(); },

  requestMatcher(req) {
    return req.url?.startsWith("/api") ||
           req.headers["x-api-request"] === "true";
  },
});

teardownHandler ​

Runs before each API reload. Use it to close connections and release resources that would otherwise leak across rebuilds:

ts
let dbConnection;

export default devSetup({
  requestHandler() { return app.callback(); },

  async teardownHandler() {
    if (dbConnection) {
      await dbConnection.close();
      dbConnection = undefined;
    }
  },
});

Without cleanup, frequent rebuilds during active development can exhaust database connections.

πŸ‘€ Inspecting Routes ​

Each route returned by createRoutes has a debug property. Enable it via DEBUG=api:

api/router.ts
ts
import { routerFactory, routes } from "_/api:factory";

const DEBUG = /\bapi\b/.test(process.env.DEBUG ?? ""); 

export default routerFactory(({ createRouter }) => {
  const router = createRouter();

  for (const { name, path, methods, middleware, debug } of routes) {
    if (DEBUG) console.log(debug.full); 
    router.register(path, methods, middleware, { name });
  }

  return router;
});
sh
DEBUG=api pnpm dev

Example output:

txt
 /api/users  [ users/index.ts ]
   methods: POST
middleware: slot: params; exec: useParams
            slot: validateParams; exec: useValidateParams
            slot: bodyparser; exec: async (ctx, next) => {
            slot: payload; exec: (ctx, next) => {
   handler: postHandler

Named middleware functions show by name; anonymous ones show their first line. Name your middleware functions - it makes this output significantly easier to read.

Individual debug properties are also available for targeted output: debug.headline, debug.methods, debug.middleware, debug.handler.

Released under the MIT License.