Each source folder serve a specific concern - marketing site, customer app, admin, etc.
Yet, development workflow is identical.
π Starting the Dev Server β
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 β
Vitecompilesapi/app.ts- Dev server starts, serving both client pages and your API routes
- Requests are routed between Vite and your API
- 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:
import { devSetup } from "_/api:factory";
import app from "./app";
export default devSetup({
requestHandler() {
return app.callback();
},
});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:
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:
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:
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;
});DEBUG=api pnpm devExample output:
/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: postHandlerNamed 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.