Lawhive Framework
Server

Server

Hono service bootstrap with health checks, request IDs, and structured logging

The @lawhive/framework-server package provides a composable service builder for Hono applications with standard infrastructure baked in: health checks, request ID propagation, and structured logging.

Installation

bun add @lawhive/framework-server

Quick Start

import { createService } from "@lawhive/framework-server/service"
import { Hono } from "hono"

const { log, bootstrap } = await createService({
  name: "my-service",
  version: "1.0.0",
})

const { app } = await bootstrap(new Hono())
  .mount(({ app, log }) => {
    app.get("/hello", (c) => c.json({ message: "Hello" }))
  })
  .build()

export default app

This gives you out of the box:

  • GET / — Service metadata (name, version, environment, instance ID)
  • GET /health — Liveness probe ({ status: "ok" })
  • GET /health/ready — Readiness probe with custom checks
  • Request ID middleware (generates or propagates X-Request-Id)
  • Structured logging via LogTape with wide event middleware

createService

The entry point for bootstrapping a service. Returns a log instance and a bootstrap function.

const { log, bootstrap } = await createService({
  name: "my-service",
  version: process.env.VERSION ?? "local",
  environment: process.env.NODE_ENV,    // defaults to NODE_ENV
  instanceId: process.env.INSTANCE_ID,  // defaults to HOSTNAME or "local"
  region: process.env.REGION,           // defaults to "local"
  options: {
    health: {
      readiness: {
        checks: {
          database: async () => {
            await prisma.$queryRaw`SELECT 1`
          },
        },
      },
      rootMeta: {
        links: {
          docs: "/api-reference",
          rpc: "/rpc",
        },
      },
    },
  },
})

Config

OptionTypeDefaultDescription
namestringService name (required)
versionstringService version (required)
environmentstringNODE_ENVDeployment environment
instanceIdstringHOSTNAMEInstance identifier
regionstring"local"Deployment region
optionsServiceOptionsHealth, logging, and request ID config

Service Builder

The bootstrap function returns a builder that lets you compose modules and mount routes:

const { app } = await bootstrap(new Hono())
  .use(myCustomModule)        // Add capabilities
  .mount(({ app, log }) => {  // Wire routes
    app.get("/tasks", (c) => c.json([]))
  })
  .build()

.use(module)

Adds a service module that extends the service context with new capabilities. Modules receive the current context and return new capabilities:

type ServiceModule<TNeeds, TAdds> = (
  ctx: ServiceContext<TNeeds>,
) => Awaitable<TAdds>

.mount(fn)

Mounts route handlers. Receives the full accumulated context from all modules:

.mount(({ app, log, meta, getRequestId }) => {
  app.get("/tasks", (c) => {
    const requestId = getRequestId(c)
    log.info("Listing tasks", { requestId })
    return c.json([])
  })
})

.build()

Executes all modules and mounts in order, returning the final ServiceContext.

Standard Capabilities

After createService, the builder starts with these capabilities:

CapabilityTypeDescription
appHonoThe Hono app instance
metaServiceMetaService metadata (name, version, etc.)
logLogLogTape logger
standardHonoStandardHealth route handlers
getRequestId(c: Context) => stringExtract request ID from context

Health Checks

Liveness

GET /health always returns 200:

{
  "status": "ok",
  "timestamp": "2026-03-10T12:00:00.000Z",
  "request_id": "abc-123"
}

Readiness

GET /health/ready runs all configured checks:

const { log, bootstrap } = await createService({
  name: "my-service",
  version: "1.0.0",
  options: {
    health: {
      readiness: {
        checks: {
          database: async () => {
            await prisma.$queryRaw`SELECT 1`
          },
          redis: async () => {
            await redis.ping()
          },
        },
      },
    },
  },
})

Returns 200 when all checks pass, 503 if any fail:

{
  "status": "ready",
  "timestamp": "2026-03-10T12:00:00.000Z",
  "request_id": "abc-123",
  "checks": {
    "database": { "status": "ok" },
    "redis": { "status": "ok" }
  }
}

Request IDs

The standard middleware automatically:

  1. Reads X-Request-Id from incoming request headers
  2. Generates a UUID if none provided
  3. Attaches it to the Hono context
  4. Returns it in the response X-Request-Id header

Access via getRequestId(c) in route handlers.

Logging

createService sets up structured logging via LogTape. The log instance is available both at the service level and in mount callbacks:

const { log, bootstrap } = await createService({ name: "my-service", version: "1.0.0" })

log.info("Service starting")

await bootstrap(new Hono())
  .mount(({ app, log }) => {
    log.info("Mounting routes")
    app.get("/", (c) => c.json({ ok: true }))
  })
  .build()

The request logging middleware automatically emits wide events for each request with timing, status code, and request ID.

Full Example

import { createService } from "@lawhive/framework-server/service"
import { Hono } from "hono"
import { cors } from "hono/cors"
import prisma from "./db"

const { log, bootstrap } = await createService({
  name: "task-service",
  version: process.env.VERSION ?? "local",
  options: {
    health: {
      rootMeta: {
        links: { rpc: "/rpc", docs: "/api-reference" },
      },
      readiness: {
        checks: {
          database: async () => { await prisma.$queryRaw`SELECT 1` },
        },
      },
    },
  },
})

const { app } = await bootstrap(new Hono())
  .mount(({ app }) => {
    app.use("/*", cors({ origin: process.env.CORS_ORIGIN || "" }))

    app.get("/tasks", async (c) => {
      const tasks = await prisma.task.findMany()
      return c.json(tasks)
    })
  })
  .build()

export default app