npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

typespec-hono

v0.20.0

Published

TypeSpec emitter: generate a Hono server, and the Zod validators it enforces, from an HTTP service definition, agreeing with the OpenAPI document @typespec/openapi3 publishes from the same source.

Downloads

2,430

Readme

typespec-hono

Generate a Hono server from a TypeSpec API definition. Routing, request validation and handler types all come from the spec.

Add @typespec/openapi3 to the same config and it writes the OpenAPI document from that same definition, so your server and your docs cannot drift apart.

Validation and types come from typespec-http-zod, which this runs for you, so your config lists one emitter.

Install

pnpm add -D typespec-hono

Peer dependencies: hono, @hono/zod-validator, zod, @typespec/compiler.

# tspconfig.yaml
emit:
  - typespec-hono
options:
  typespec-hono:
    emitter-output-dir: "{project-root}/src/generated"
    seal-object-schemas: true

Quick start

You write four files. Everything under src/generated/ is produced by the compiler and never edited:

main.tsp              your API definition
tspconfig.yaml        which emitters to run
src/
  generated/          written by `tsp compile`, never edited by hand
    app.gen.ts
    runtime.gen.ts
    schemas.gen.ts
  deps.ts             your application's answers
  index.ts            your handlers, and the app

main.tsp

import "@typespec/http";
using Http;

@service(#{ title: "Widgets" })
namespace Widgets;

model Widget {
  id: string;
  name: string;
}

@route("/widgets")
interface WidgetRoutes {
  @get list(@query limit?: int32): Widget[];
  @get read(@path id: string): Widget;
}
pnpm exec tsp compile .

src/index.ts

input and the return type are both known from the spec, so a handler that does not match the contract does not compile:

import { Hono } from "hono";
import { registerRoutes } from "./generated/app.gen.js";
import { deps } from "./deps.js";

const handlersFor = () => ({
	WidgetRoutes_list: (ctx, input) => widgets.slice(0, input.limit ?? 20),
	WidgetRoutes_read: (ctx, input) => widgets.find((w) => w.id === input.id),
});

export default registerRoutes(new Hono(), handlersFor, deps);

Leave handlersFor unannotated: annotating it widens the value and disables the check that catches a handler for an operation the spec no longer declares. It is a factory rather than an object because a Workers service binding lives on c.env and exists only for the duration of a request.

src/deps.ts

Six hooks, each answering something the spec does not contain:

| hook | the spec says | you say | | --------------- | ------------------------------------------- | ------------------------------------ | | authorize | which schemes and scopes an operation needs | whether this caller satisfies them | | context | whether a caller is required | who the caller is | | noContext | | what to answer when there is not one | | notAcceptable | which media types are offered | what to answer when none match | | invalid | the schema | what a validation failure looks like | | respond | every status arm and its schema | which arm this result is |

import type { RouteDeps } from "./generated/runtime.gen.js";

export const deps: RouteDeps = {
	authorize: (requirements) => async (c, next) => {
		await next();
	},
	context: (c) => ({ userId: c.req.header("x-user") }),
	noContext: (c) => c.json({ error: "unauthorized" }, 401),
	notAcceptable: (c, offered) => c.json({ error: "not_acceptable", offered }, 406),
	invalid: (result, c) => (result.success ? undefined : c.json({ error: "invalid" }, 400)),
	respond: (c, arms, result) => c.json(result as never, 200),
};

Routing, request validation and the handler types come from the spec. What is left is the four files above.

What it emits

Into your output directory:

| file | what it is | | ---------------------- | ------------------------------------------------------------------------------ | | app.gen.ts | the server: routes, validators, and the handler interface you implement | | runtime.gen.ts | the types your deps implements against, and the helpers the server calls | | schemas.gen.ts | a Zod schema for every request and response, and the status arms each declares | | vocabularies.gen.ts | shared enums, where the spec declares them | | requests.gen.ts | request types, when contracts-output-dir is set | | wire-contract.gen.ts | assertions that the schemas and the types agree, with the same option |

The last four are typespec-http-zod's; see its README for what they contain.

Your own code imports from runtime.gen.ts:

import type { Ctx, RouteDeps } from "./generated/runtime.gen.js";

Setting runtime-module replaces it with a module of your own, and it is then not written.

Routes are grouped into a sub-app per resource and mounted with app.route(), following Hono's best-practices guide. Handlers are written directly after the path definitions rather than lifted into separate controller files, because a handler in another file cannot infer its path parameters. A resource with a single route gets no sub-app.

The output is plain Hono and @hono/zod-validator, not @hono/zod-openapi. That package generates a document from the code, which would compete with the one openapi3 publishes from the spec.

import { Hono } from "hono";
import { registerRoutes } from "./generated/app.gen.js";

// Leave handlersFor unannotated. Annotating it widens the value and disables the
// exhaustiveness check that catches a handler for an operation the spec no longer declares.
const handlersFor = (c) => backendFor(c.env);
const routes = registerRoutes(new Hono<AppEnv>(), handlersFor, deps);

export default routes;

handlersFor is a factory rather than an object because a Workers service binding lives on c.env and exists only for the duration of a request.

Docs

  • Guides: middleware, the RPC client, authentication, base paths, HEAD operations, request bodies, streaming, observability
  • Cloudflare Workers: which router to pick, and what the bundle costs
  • Reference: every option, every diagnostic, and the known limits

Licence

MIT