← Back to DevBytes

Building Full-Stack Apps with Hono

Building Full-Stack Apps with Hono

Hono is a small, simple, and ultrafast web framework for building applications on the edge. Originally designed for Cloudflare Workers, it now supports Deno, Bun, and Node.js, making it a versatile choice for modern full‑stack development. With a built-in router, middleware system, and first-class TypeScript support, Hono enables you to create both API backends and server-rendered frontends with minimal boilerplate.

What is Hono?

Hono (Japanese for "framework") is a lightweight HTTP framework that focuses on performance and developer experience. It provides a clean, Express‑like API but with zero dependencies and a tiny footprint (under 10 KB). Key features include:

Why Use Hono for Full-Stack Apps?

Full‑stack applications require both an API and a frontend. Hono excels because it can handle both sides efficiently:

Getting Started

Install Hono via your preferred package manager. The following examples use Node.js with npm, but the same code works on Deno and Bun.

npm install hono

Create a basic server:

import { Hono } from 'hono'
const app = new Hono()

app.get('/', (c) => c.text('Hello Hono!'))

export default app

For Node.js, you need a server adapter. Hono provides @hono/node-server:

npm install @hono/node-server
import { serve } from '@hono/node-server'
import { Hono } from 'hono'

const app = new Hono()
app.get('/', (c) => c.text('Hello from Hono on Node!'))

serve(app, (info) => {
  console.log(`Listening on http://localhost:${info.port}`)
})

Building a Full-Stack Application

We'll build a simple note‑taking app with a JSON API and a static frontend that uses hono/serve-static and hono/jsx for server‑side rendering.

1. Project Structure

my-app/
├── src/
│   ├── index.ts          # Entry point
│   ├── routes/
│   │   ├── notes.ts      # API routes
│   │   └── pages.ts      # SSR routes
│   └── db.ts             # Simple in‑memory store
├── public/
│   └── index.html        # Fallback static file
└── package.json

2. Database (In-Memory for Demo)

// src/db.ts
type Note = {
  id: string
  title: string
  content: string
  createdAt: string
}

const notes: Note[] = []

export const getNotes = () => notes

export const addNote = (title: string, content: string): Note => {
  const note: Note = {
    id: crypto.randomUUID(),
    title,
    content,
    createdAt: new Date().toISOString(),
  }
  notes.push(note)
  return note
}

export const deleteNote = (id: string): boolean => {
  const index = notes.findIndex(n => n.id === id)
  if (index === -1) return false
  notes.splice(index, 1)
  return true
}

3. API Routes

// src/routes/notes.ts
import { Hono } from 'hono'
import { getNotes, addNote, deleteNote } from '../db'

const notesApp = new Hono()

notesApp.get('/', (c) => {
  const allNotes = getNotes()
  return c.json(allNotes)
})

notesApp.post('/', async (c) => {
  const { title, content } = await c.req.json()
  if (!title || !content) {
    return c.json({ error: 'Title and content required' }, 400)
  }
  const note = addNote(title, content)
  return c.json(note, 201)
})

notesApp.delete('/:id', (c) => {
  const id = c.req.param('id')
  const deleted = deleteNote(id)
  if (!deleted) {
    return c.json({ error: 'Note not found' }, 404)
  }
  return c.json({ message: 'Deleted' })
})

export default notesApp

4. Server‑Side Rendering with JSX

Hono has a built‑in JSX engine. Create a simple page that lists notes.

// src/routes/pages.ts
import { Hono } from 'hono'
import { getNotes } from '../db'

const pagesApp = new Hono()

pagesApp.get('/', (c) => {
  const notes = getNotes()
  return c.html(
    <html>
      <head><title>Notes App</title></head>
      <body>
        <h1>My Notes</h1>
        <ul>
          {notes.map(note => (
            <li>
              <strong>{note.title}</strong> – {note.content}
            </li>
          ))}
        </ul>
      </body>
    </html>
  )
})

export default pagesApp

To use JSX, add "jsx": "react-jsx" to your tsconfig.json (or use .tsx files). Hono also supports hono/html for template literals if you prefer.

5. Static Files and Middleware

Serve static files (CSS, client-side JS) from the public directory.

// src/index.ts
import { serve } from '@hono/node-server'
import { Hono } from 'hono'
import { serveStatic } from '@hono/node-server/serve-static'
import { logger } from 'hono/logger'
import { cors } from 'hono/cors'
import notesApp from './routes/notes'
import pagesApp from './routes/pages'

const app = new Hono()

app.use('*', logger())
app.use('/api/*', cors())  // Allow CORS for API

// Mount sub‑routers
app.route('/api/notes', notesApp)
app.route('/pages', pagesApp)

// Serve static files from 'public'
app.use('/static/*', serveStatic({ root: './public' }))

// Fallback to index.html for client‑side routing
app.get('*', serveStatic({ path: './public/index.html' }))

serve(app, (info) => {
  console.log(`Server running on http://localhost:${info.port}`)
})

The public/index.html file can contain a simple client‑side app that fetches from /api/notes.

Best Practices

Deployment

Hono apps can be deployed to multiple platforms:

Conclusion

Hono provides a refreshingly simple yet powerful foundation for building full‑stack applications. Its minimal footprint, blazing speed, and broad platform support make it an excellent choice for developers who want to focus on logic rather than configuration. By combining Hono’s routing, middleware, and SSR capabilities, you can create modern APIs and interactive frontends with ease. Whether you are building a small prototype or a production‑grade edge application, Hono gives you the tools to do it efficiently and cleanly. Start with a simple CRUD example, then expand with authentication, database integrations, and client‑side frameworks—all while keeping your codebase lean and maintainable.

🚀 Need a reliable AI agent for your project?

Deploy Hermes Agent in 10 minutes. Managed hosting, zero DevOps.

Get Started — $23.99/mo
← Back to all articles