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

hono-rbac

v0.1.0

Published

Edge-ready Role-Based Access Control for Hono

Readme

hono-rbac

Edge-ready Role-Based Access Control for Hono

npm version license Edge Ready Test Coverage


Features

  • Tiny – < 5 KB core, zero Node built-ins
  • Edge-safe – works on Cloudflare Workers, Deno, Bun, Node
  • DX-firstguardHelpers.role("admin") and guardHelpers.auth() one-liners
  • Type-safe – manifest-driven permissions
  • Composable – logical algebra (anyOf, allOf, not)
  • Explainableexplain() traces every decision

Quick Start

Installation

npm install hono-rbac
# or
pnpm add hono-rbac

Basic Setup

import { Hono } from 'hono'
import { createRBAC, guard, perm, anyOf, guardHelpers } from 'hono-rbac'

// 1. Define roles and permissions
const roles = {
  reader: ['post.read:any'],
  author: ['post.read:any', 'post.write:own'],
  admin: ['*.**'], // Admin wildcard - access to everything
}

// 2. Create RBAC engine
const rbac = createRBAC({ roles })

// 3. Create Hono app
const app = new Hono()

// 4. Inject user and RBAC into context
app.use('*', async (c, next) => {
  const user = { id: 'u1', roles: ['author'] }
  c.set('user', user)
  c.set('rbac', rbac.forUser(user))
  c.set('rbacEngine', rbac)
  await next()
})

// 5. Protect routes with guards
app.get('/posts', guard(perm('post.read:any')), (c) => c.json({ posts: [] }))

// Or use sugar helpers
app.get('/admin', guardHelpers.role('admin'), (c) => c.json({ message: 'Admin area' }))

Guard Helpers Cheatsheet

| Helper | Usage | Description | | ---------------------------------------------- | -------------- | ---------------------------------- | | guardHelpers.auth() | Requires login | User must be authenticated | | guardHelpers.role('admin') | Single role | User must have specific role | | guardHelpers.roles.any('editor', 'admin') | Any role | User must have at least one role | | guardHelpers.roles.all('editor', 'reviewer') | All roles | User must have all roles | | guardHelpers.perm('billing.refund') | Permission | User must have specific permission | | guardHelpers.can.read('post') | CRUD helper | Read permission (any or own) | | guardHelpers.can.create('post') | CRUD helper | Create permission | | guardHelpers.can.update.own('post', ...) | CRUD helper | Update own resource | | guardHelpers.ownOrAdmin('post', ...) | Ownership | Owner or admin check |


Permission Format

Permissions follow the format: resource.action:scope

Examples:

  • post.read:any - Read any post
  • post.write:own - Write own posts
  • post.** - All actions on posts
  • *.** - Admin wildcard (everything)
  • role.admin - Role-based check

Logical Operators

Compose complex permission checks using logical operators:

import { perm, anyOf, allOf, not } from 'hono-rbac'

// OR - at least one must match
guard(anyOf(perm('post.read:any'), perm('post.write:own')))

// AND - all must match
guard(allOf(perm('post.read'), perm('comment.read')))

// NOT - negation
guard(not(perm('post.delete')))

Resource Loaders

Preload resources into context for ownership checks:

import { load } from 'hono-rbac'

// Load resource
const loadPost = load.post(async (c) => {
  const id = c.req.param('id')
  const post = await db.getPost(id)
  c.set('post', post)
})

// Guard with ownership check
app.put(
  '/posts/:id',
  loadPost,
  guard(
    perm('post.write:own', (c, user) => {
      const post = c.get('post')
      return post.authorId === user.id
    })
  ),
  async (c) => {
    // Handle update
  }
)

Or use the helper:

app.put(
  '/posts/:id',
  loadPost,
  guardHelpers.ownOrAdmin('post', (p, u) => p.authorId === u.id),
  (c) => c.text('updated!')
)

Explain Decisions (Debug Mode)

Debug permission checks with detailed traces:

const rbac = createRBAC({ roles })
const userRbac = rbac.forUser(user)

const explanation = await userRbac.explain(perm('post.write:own'), c)

console.log(explanation)
// {
//   allow: true,
//   reason: "Access granted. User has 2 permission(s) from roles: author",
//   trace: [...]
// }

Type-Safe Manifest

Define your permissions with full TypeScript type safety:

import { defineManifest } from 'hono-rbac'

export const manifest = defineManifest({
  resources: ['post', 'comment', 'org'] as const,
  actions: ['read', 'write', 'delete', 'refund'] as const,
  scopes: ['own', 'any'] as const,
})

Autocompletion & validation for permission strings is generated automatically.


Complete Example

import { Hono } from 'hono'
import { createRBAC, guardHelpers, load } from 'hono-rbac'

const roles = {
  author: ['post.read:any', 'post.write:own'],
  admin: ['*.**'],
}

const app = new Hono()
const rbac = createRBAC({ roles })

app.use('*', (c, next) => {
  c.set('user', { id: 'u1', roles: ['author'] })
  c.set('rbac', rbac.forUser(c.get('user')))
  c.set('rbacEngine', rbac)
  return next()
})

const loadPost = load.post(async (c) => {
  const post = { id: c.req.param('id'), authorId: 'u1' }
  c.set('post', post)
})

app.put(
  '/posts/:id',
  loadPost,
  guardHelpers.ownOrAdmin('post', (p, u) => p.authorId === u.id),
  (c) => c.text('updated!')
)

export default app

Project Structure

hono-rbac/
├── src/
│   ├── index.ts           # Public API
│   ├── types.ts           # TypeScript types
│   ├── permissions.ts     # Permission algebra
│   ├── engine.ts          # Evaluation engine
│   ├── guard.ts           # RBAC engine & guard middleware
│   ├── sugar.ts           # Helper functions
│   ├── loader.ts          # Resource loaders
│   ├── explain.ts         # Decision explanations
│   ├── manifest.ts        # Type-safe manifests
│   └── store/
│       └── memory.ts      # In-memory role store
├── examples/
│   ├── src/
│   │   ├── types.ts       # Shared example types
│   │   ├── hono-basic/
│   │   │   └── hono-basic.ts
│   │   └── hono-own-admin/
│   │       └── hono-own-admin.ts
│   └── package.json       # Example dependencies
├── dist/                  # Compiled output
└── coverage/              # Test coverage reports

Philosophy

| Principle | Description | | ------------------- | --------------------------------------------- | | DX-first | Most routes should be protectable in one line | | Functional core | Pure evaluation, no side-effects | | Explainable | Every decision traceable | | Composable | Logical algebra over flat strings | | Portable | Runs everywhere (Edge, Deno, Bun, Node) |


Test Coverage

Current coverage: 99.76% (essentially 100% of testable code)

  • Statements: 99.76%
  • Branches: 98.63%
  • Functions: 97.91%
  • Lines: 99.76%

All executable code paths are fully tested. The only uncovered files are index.ts (export file) and types.ts (type definitions), which contain no executable runtime code.


Running Examples

Basic Example

pnpm install
pnpm run dev

Then test with:

# As reader (can only read)
curl -H "X-User-Id: u1" -H "X-User-Role: reader" http://localhost:3000/posts

# As admin (can do everything)
curl -H "X-User-Id: u3" -H "X-User-Role: admin" http://localhost:3000/admin

Own-or-Admin Example

pnpm run dev:own-admin

Development

# Install dependencies
pnpm install

# Run all tests
pnpm test

# Run tests with coverage
pnpm test:coverage

# Watch mode
pnpm test:watch

# Build the project
pnpm run build

# Linting
pnpm run lint              # Run all linting (types + eslint)
pnpm run lint:types        # TypeScript type checking only
pnpm run lint:eslint       # ESLint code quality checks
pnpm run lint:fix          # Auto-fix ESLint issues

# Formatting
pnpm run format            # Auto-format all code
pnpm run format:check      # Check formatting without changes

# Comprehensive checks
pnpm run check             # Run all checks (lint + format + test)
pnpm run fix               # Auto-fix all issues (lint + format)

Documentation


License

MIT © Zay Collier


See Also


Contributing

Contributions are welcome! Please ensure:

  • All tests pass (pnpm test)
  • Coverage remains above 99% (pnpm test:coverage)
  • Code is properly linted (pnpm run lint)
  • Code is properly formatted (pnpm run format:check)
  • Or simply run pnpm run check to verify everything at once
  • Use pnpm run fix to auto-fix linting and formatting issues

Built for the Hono community