loke.dev
Header image for Stop Using Express (Build Your Next API on Hono Instead)

Stop Using Express (Build Your Next API on Hono Instead)

It’s time to retire the 2010-era middleware patterns for a type-safe, runtime-agnostic standard that runs anywhere from the Edge to a bare-metal Node.js server.

· 4 min read

Why are we still building modern backend architectures on top of a framework that was conceived when the iPhone 4 was cutting-edge technology?

Express is the undisputed king of Node.js. It has the downloads, the stack overflow answers, and the nostalgic comfort of a well-worn pair of sneakers. But let's be honest: those sneakers have holes in the soles. If you’re building a new API today, sticking with Express is like buying a brand-new car but insisting it has a cassette player. It’s time to talk about Hono.

The Express Debt

Don't get me wrong, I’ve written enough Express to fill a library. But the cracks are hard to ignore now. Express wasn't built for the modern world of TypeScript, Edge Computing, or Web Standard APIs.

To get proper type safety in Express, you have to jump through hoops, manually define interfaces for your requests, and pray your middleware doesn't mutate the req object in ways your IDE can't track. Plus, it’s tied to the Node.js runtime. Try running Express on a Cloudflare Worker or Bun without a massive compatibility shim—it’s not a fun Saturday afternoon.

Enter Hono: The "Small" Framework That’s Actually Huge

Hono (which means "flame" in Japanese) is fast. Like, "I had to check the logs to make sure it actually ran" fast. But speed isn't even its best feature. It’s built on Web Standards (fetch, Request, Response) and treats TypeScript like a first-class citizen, not an annoying neighbor.

Here is what a basic Hono server looks like:

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

app.get('/', (c) => {
  return c.text('Hono is burning bright!')
})

export default app

Notice that c? That’s the Context. It replaces the req and res objects. Instead of juggling two massive objects, everything you need—parameters, body, headers, and the response helpers—is right there in one place.

Type Safety Without the Tears

The biggest "Aha!" moment with Hono comes when you realize you don't have to guess what's in your request. If you use the Zod validator (which is officially supported via middleware), your types flow from your validation schema directly into your route handler.

Check this out:

import { z } from 'zod'
import { zValidator } from '@hono/zod-validator'

const schema = z.object({
  id: z.string().uuid(),
  title: z.string().min(1)
})

app.post('/posts', zValidator('json', schema), (c) => {
  // validated is fully typed!
  const validated = c.req.valid('json')
  
  return c.json({
    message: `Created post: ${validated.title}`,
    id: validated.id
  })
})

If you try to access a property that wasn't in your schema, your IDE will yell at you. In Express, you'd probably be staring at undefined in production before realizing you made a typo.

"But I'm on Node.js!"

One of the best things about Hono is that it’s runtime agnostic. It doesn't care if you're on Node, Deno, Bun, Cloudflare Workers, or some futuristic toaster running V8.

If you want to stay on Node, you just use the @hono/node-server adapter:

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

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

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

This flexibility is a superpower. You can start your project on a standard VPS and migrate to the Edge (like Cloudflare) later just by changing the entry point. No rewriting your entire routing logic.

The Middleware Experience

In Express, middleware often feels like a black box. You call next(), and you hope for the best. In Hono, middleware is predictable and easy to compose. Because it uses async/await properly (unlike the early days of Express), you can actually see the flow of data.

Here’s a simple logger I wrote the other day:

app.use('*', async (c, next) => {
  const start = Date.now()
  await next()
  const end = Date.now()
  console.log(`${c.req.method} ${c.req.url} - ${end - start}ms`)
})

It’s clean, it’s readable, and it works exactly how you expect. No weird on-finished event listeners required.

The Reality Check: Is there a catch?

Is Hono perfect? Of course not. The ecosystem isn't as massive as Express. If you’re looking for a very specific, niche middleware written in 2016, you probably won't find a Hono-specific version of it.

However, because Hono follows Web Standards, many modern libraries "just work." And honestly, building your own middleware in Hono is so much easier that the "missing" ecosystem rarely feels like a blocker.

Wrapping Up

We’ve spent a decade refining how we build for the web. Our tools should reflect that progress. Express served us well—it's a legend for a reason—but it’s a legacy tool in a modern world.

If you want:
1. Full TypeScript support that actually works.
2. Runtime flexibility (deploy anywhere).
3. Better performance with a smaller footprint.
4. Standard-compliant code that will age better.

Then give Hono a shot on your next side project. It's refreshing to use a tool that works with you instead of making you fight for every type definition. Plus, the developer experience is so much better that you might actually enjoy writing CRUD endpoints again.

Go ahead, try it: npm create hono@latest. Your future self (and your IDE) will thank you.