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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@avratz/fp-toolkit-hono

v1.0.1

Published

Functional programming oriented wrapper for Hono, providing helpers and a clean archetype for building type-safe APIs.

Readme

@avratz/fp-toolkit-hono

A functional-programming oriented wrapper for Hono, offering opinionated helpers and a clean archetype for building type-safe APIs. It promotes composability, explicit error handling with fp-ts, and a clean separation between routing, controllers, and services.


Features

  • Functional core, imperative shell scaffolding
  • Minimal Hono wrapper with a tiny surface area
  • First-class fp-ts integration (TaskEither-driven handlers)
  • Typed JSON validation middleware (Zod-compatible)
  • Ergonomic helpers: createApp, createRoute, validateJson, runTaskEither, AppError
  • Encourages clear DI boundaries (controllers receive deps, services depend on ports)

Works great for teams that want predictable flows, strong typing, and consistent architecture across services.


Installation

npm install @avratz/fp-toolkit-hono hono fp-ts zod
# or
pnpm add @avratz/fp-toolkit-hono hono fp-ts zod

Peer assumptions

  • Node.js 18+
  • TypeScript project
  • fp-ts for effects and zod (or compatible) for validation

Quick start

1) App bootstrap

Create your entrypoint and register controllers via routes:

// app.ts
import { createApp } from '@avratz/fp-toolkit-hono'
import { routes } from './routes'

const app = createApp()
routes.forEach(({ route, controller }) => app.route(route, controller))
export default app

A typical routes module exports something like:

// routes.ts
import { userController } from './user.controller'
import { makeUserRepo } from './adapters/user-repo'

export const routes = [
	{
		route: '/users',
		controller: userController({ userRepo: makeUserRepo() }),
	},
] as const

2) Controller (routing + orchestration)

Controllers define endpoints and compose services. They remain thin and side-effect free except for wiring.

// user.controller.ts
import { createRoute, validateJson, runTaskEither } from '@avratz/fp-toolkit-hono'
import { createUserService } from './user.service'
import { CreateUserBody } from './domain/user.type'
import type { TCreateUserBody } from './domain/user.type'
import type { UserRepository } from './port/user-repository.port'

// Add parsed body to typed env
type CreateUserEnv = { Variables: { body: TCreateUserBody } }

export function userController(deps: { userRepo: UserRepository }) {
	const route = createRoute<CreateUserEnv>()
	const service = createUserService(deps.userRepo)

	route.post('/', validateJson(CreateUserBody), (context) => {
		return runTaskEither(service.registerUser(context.var.body.name))(context)
	})

	route.get('/:id', (context) => {
		return runTaskEither(service.getUser(context.req.param('id')))(context)
	})

	return route
}

3) Service (business logic)

Services expose pure, composable functions that return TaskEither<AppError, A>.

// user.service.ts
import { AppError } from '@avratz/fp-toolkit-hono'
import { TaskEither } from 'fp-ts/lib/TaskEither'
import type { UserRepository } from './port/user-repository.port'
import type { TUser } from './domain/user.type'

interface UserService {
	getUser: (id: string) => TaskEither<AppError, TUser>
	registerUser: (name: string) => TaskEither<AppError, TUser>
}

export const createUserService = (repo: UserRepository): UserService => ({
	getUser: (id: string) => repo.findById(id),
	registerUser: (name: string) => repo.create(name),
})

Concepts & Flow

Request lifecycle

  1. validateJson(schema) parses and validates the request body (e.g., with Zod), storing the typed value in context.var.body.
  2. Controller calls a service method → returns TaskEither<AppError, A>.
  3. runTaskEither(te)(context) executes the effect and serializes a success or error HTTP response in a uniform format.

Why ``?

  • Encodes success/failure in the type system
  • Composable chains without try/catch
  • Predictable, testable logic

API Reference (high-level)

The public API is intentionally small. Below are the core helpers.

createApp()

Creates a Hono app pre-configured for the FP toolkit conventions.

Returns: Hono instance


createRoute<Env = {}>()

Creates a typed route/controller instance.

Type params

  • Env: extends Hono Env (use this to add Variables like body)

Returns: { get, post, put, patch, delete, route, ... } routing methods


validateJson(schema)

Middleware that parses JSON and validates against a schema (Zod-compatible). On success, injects context.var.body with the inferred type.

Schema requirements: an object with a parse/safeParse interface (e.g., Zod).


runTaskEither(te)

Adapter to execute a TaskEither<AppError, A> and send an HTTP response.

Usage: runTaskEither(service.someOp(args))(context)

Behavior

  • Right<A> → 2xx JSON body
  • Left<AppError> → mapped to structured error response (status + message)

AppError

Discriminated union (or branded error) consumed by runTaskEither to standardize failures (e.g., BadRequest, NotFound, Conflict, Internal).

Tip: expose constructor helpers like badRequest(msg), notFound(msg), etc., to make error creation consistent.


Validation example (with Zod)

import { z } from 'zod'

export const CreateUserBody = z.object({
	name: z.string().min(1),
})
export type TCreateUserBody = z.infer<typeof CreateUserBody>
route.post('/', validateJson(CreateUserBody), (c) =>
	runTaskEither(service.registerUser(c.var.body.name))(c),
)

Suggested project layout

src/
  app.ts
  routes.ts
  user/

    user.controller.ts
    user.service.ts
    domain/
      user.type.ts
      user.core.ts
    port/
      user-repository.port.ts
    adapters/
      user.repository.ts
  • domain/: types, schemas, core functions (pure)
  • port/: interfaces for external systems
  • adapters/: implementations (DB, HTTP clients, etc.)
  • controllers: wiring + http layer
  • services: business rules (pure/TE)

Error handling

  • Define an AppError algebra for your app
  • Map domain errors to HTTP statuses in one place (inside runTaskEither or a custom encoder)
  • Prefer small, composable TaskEither chains over large try/catch blocks

Testing

  • Services: test as pure functions returning TaskEither (use in-memory ports)
  • Controllers: test route handlers by invoking them with a mock context
  • Validation: test Zod schemas independently

Versioning

Follows SemVer. Breaking changes will bump the major version.


Contributing

PRs welcome! Please discuss large changes via issues first.


License

MIT © Avratz