---
title: "Hono"
description: "Serve Markdown over HTTP from a Hono server with Comark Content."
canonical_url: "https://content.comark.dev/integrations/hono"
---
# Hono

> Serve Markdown over HTTP from a Hono server with Comark Content.

Hono hands you the raw web-standard `Request` as `c.req.raw`, so [`content.handler()`](https://content.comark.dev/reference/content/handler) mounts as a one-line wildcard route. At the end of this guide you have `/api/content/get/about` answering with a parsed page, and a route that returns page data by slug.

You need a Hono project on Node, Bun, or Deno. Create one with `npm create hono@latest` if you're starting fresh. The `fs` source needs a filesystem, so for Cloudflare Workers and other edge runtimes read from [GitHub](https://content.comark.dev/sources/github), a [storage driver](https://content.comark.dev/sources/unstorage), or a [snapshot](https://content.comark.dev/deployment/with-a-snapshot) instead.

::steps{level="3"}
### Install the package

  :::code-group
  ```bash [pnpm]
  pnpm add comark-content
  ```

  ```bash [npm]
  npm install comark-content
  ```

  ```bash [yarn]
  yarn add comark-content
  ```

  ```bash [bun]
  bun add comark-content
  ```
  :::

### Write a Markdown file

```md [content/about.md]
---
title: About
---

# About us

We write documentation for a living.
```

### Create the Content instance

```ts [src/content.ts]
import { comarkContent } from 'comark-content'
import fs from 'comark-content/sources/fs'

export const content = comarkContent({
  source: fs('./content'),
})
```

### Mount the handler

```ts [src/index.ts]
import { Hono } from 'hono'
import { content } from './content'

const app = new Hono()

app.all('/api/content/*', (c) => content.handler(c.req.raw))

export default app
```

Start the server and read a page:

```bash [Terminal]
pnpm dev
curl http://localhost:3000/api/content/get/about
```

You get the page as JSON: its `path`, `data` with `title: "About"`, and `nodes`. That's the whole data surface: `GET /api/content/get/<path>`, `/api/content/list`, `/api/content/navigation`, plus any [custom sections](https://content.comark.dev/reference/content/add-serve-handler) you register.

### Read content from other routes

Routes on the same server skip HTTP and call the instance directly:

```ts [src/index.ts]
app.get('/pages/:slug', async (c) => {
  const page = await content.get(`/${c.req.param('slug')}`)
  if (!page) return c.notFound()

  return c.json(page)
})
```

To return HTML instead of JSON, render `page` with [`@comark/html`](https://comark.dev/rendering/html) as the [Node guide](https://content.comark.dev/integrations/node#render-to-html) shows, and pass the string to `c.html()`.

### See edits while you develop

The instance parses a file once and keeps the result in memory, so an edit isn't visible until you tell it. Start a watcher in development:

```ts [src/index.ts]
if (process.env.NODE_ENV !== 'production') {
  await content.watch()
}
```

[Watch content changes](https://content.comark.dev/guide/watch) explains what the watcher does.
::

You have a working setup. Everything below is optional; pick what you need.

## Consume it from a front-end

Any browser app talks to the mounted handler through the client, with the same `get()`, `list()`, and `navigation()` methods. Pass `baseURL` when the Hono server runs on another origin:

```ts [content-client.ts]
import { createContentClient } from 'comark-content/client'

export const contentClient = createContentClient({
  baseURL: 'https://api.example.com',
})
```

[Client and handler](https://content.comark.dev/advanced/client-and-handler) explains how the two meet.

## Deploy

- **Node, Bun, or Deno on a host that deploys your whole project** keeps working with `fs('./content')`. Start the process from the project root, or pass an absolute path. [Deploy with content files](https://content.comark.dev/deployment/with-content-files) has the checklist.
- **Cloudflare Workers, Deno Deploy, and other edge runtimes** have no filesystem. Read from a [GitHub repository](https://content.comark.dev/sources/github) or a [storage driver](https://content.comark.dev/sources/unstorage) at request time, or build a [snapshot](https://content.comark.dev/deployment/with-a-snapshot) and load it with a `snapshot()` source from a bundled asset or a URL.

## Going further

::card-group{cols="2"}
  :::card{icon="i-lucide-plug" title="Client and handler" to="https://content.comark.dev/advanced/client-and-handler"}
  How the handler and client pair works, and how plugins extend it.
  :::

  :::card{icon="i-lucide-refresh-cw" title="Keep remote content up to date" to="https://content.comark.dev/deployment/remote-content"}
  Refresh content read from GitHub or a bucket without a redeploy.
  :::
::


## Sitemap

See the full [sitemap](https://content.comark.dev/sitemap.md) for all pages.
