Documentation Index Fetch the complete documentation index at: https://mintlify.com/unkeyed/unkey/llms.txt
Use this file to discover all available pages before exploring further.
Overview
This guide covers everything you need to protect your Hono application with Unkey using the official @unkey/hono middleware. Hono works everywhere: Node.js, Bun, Cloudflare Workers, and more.
What you’ll learn:
Quick setup with @unkey/hono
Protecting specific routes
Custom error handling
Permission-based access
Deployment patterns
Prerequisites
Quick Start
Create a Hono app
npm create hono@latest unkey-hono
cd unkey-hono
Choose your preferred runtime when prompted.
Install the Unkey middleware
Add your root key
Create a .env file: UNKEY_ROOT_KEY = "unkey_..."
The Hono middleware verifies keys directly against your root key.
Add the middleware
Update src/index.ts: import { Hono } from "hono" ;
import { unkey , UnkeyContext } from "@unkey/hono" ;
// Type the context so you get autocomplete for c.get("unkey")
const app = new Hono <{ Variables : { unkey : UnkeyContext } }>();
// Protect all routes with API key authentication
app . use ( "*" , unkey ({
rootKey: process . env . UNKEY_ROOT_KEY ! ,
}));
// This route now requires a valid API key
app . get ( "/" , ( c ) => {
// Access verification result from context
const keyInfo = c . get ( "unkey" );
return c . json ({
message: "Hello from protected route!" ,
keyId: keyInfo . keyId ,
valid: keyInfo . valid ,
});
});
// Another protected route
app . get ( "/secret" , ( c ) => {
const keyInfo = c . get ( "unkey" );
return c . json ({
secret: "data" ,
identity: keyInfo . identity ,
meta: keyInfo . meta ,
});
});
export default app ;
Test it
Create a test key in your Unkey dashboard , then: curl http://localhost:3000 \
-H "Authorization: Bearer YOUR_API_KEY"
You should see: {
"message" : "Hello from protected route!" ,
"keyId" : "key_..." ,
"valid" : true
}
Without a key, you’ll get a 401: curl http://localhost:3000
Protecting Specific Routes
Instead of protecting all routes, apply middleware to specific paths:
import { Hono } from "hono" ;
import { unkey , UnkeyContext } from "@unkey/hono" ;
const app = new Hono <{ Variables : { unkey : UnkeyContext } }>();
// Public route — no middleware
app . get ( "/" , ( c ) => {
return c . json ({ message: "Welcome! This route is public." });
});
// Protected routes — apply middleware to /api/* paths only
app . use ( "/api/*" , unkey ({ rootKey: process . env . UNKEY_ROOT_KEY ! }));
app . get ( "/api/secret" , ( c ) => {
const keyInfo = c . get ( "unkey" );
return c . json ({ secret: "data" , keyId: keyInfo . keyId });
});
app . get ( "/api/user" , ( c ) => {
const keyInfo = c . get ( "unkey" );
return c . json ({ identity: keyInfo . identity });
});
export default app ;
Middleware Options
Customize the middleware behavior:
app . use ( "*" , unkey ({
rootKey: process . env . UNKEY_ROOT_KEY ! , // Required: Your root key
// Optional: Custom error handling
onError : ( c , error ) => {
console . error ( "Unkey error:" , error );
return c . json ({ error: "Auth service unavailable" }, 503 );
},
// Optional: Custom unauthorized response
handleInvalidKey : ( c , result ) => {
return c . json ({
error: "Invalid API key" ,
code: result . code
}, 401 );
},
}));
By default, the middleware looks for Authorization: Bearer <token>. You can customize this:
app . use ( "/api/*" , unkey ({
rootKey: process . env . UNKEY_ROOT_KEY ! ,
getKey : ( c ) => c . req . header ( "x-api-key" ), // Custom header
}));
Permission-Based Routes
Require specific permissions for certain routes:
import { Hono } from "hono" ;
import { unkey , UnkeyContext } from "@unkey/hono" ;
const app = new Hono <{ Variables : { unkey : UnkeyContext } }>();
// Basic auth for all API routes
app . use ( "/api/*" , unkey ({ rootKey: process . env . UNKEY_ROOT_KEY ! }));
// Public data endpoint
app . get ( "/api/data" , ( c ) => {
return c . json ({ data: "public" });
});
// Admin endpoint with permission check
app . delete ( "/api/users/:id" , async ( c ) => {
const keyInfo = c . get ( "unkey" );
// Check if user has admin permission
if ( ! keyInfo . permissions ?. includes ( "admin.delete" )) {
return c . json ({ error: "Admin access required" }, 403 );
}
const userId = c . req . param ( "id" );
// Delete user logic here
return c . json ({ deleted: userId });
});
export default app ;
Or verify permissions during the initial key check:
import { Unkey } from "@unkey/api" ;
const requireAdmin = async ( c : Context , next : Next ) => {
const authHeader = c . req . header ( "Authorization" );
const apiKey = authHeader ?. replace ( "Bearer " , "" );
if ( ! apiKey ) {
return c . json ({ error: "Missing API key" }, 401 );
}
const unkey = new Unkey ({ rootKey: process . env . UNKEY_ROOT_KEY ! });
const { data } = await unkey . keys . verifyKey ({
key: apiKey ,
permissions: [ "admin" ],
});
if ( ! data . valid ) {
if ( data . code === "INSUFFICIENT_PERMISSIONS" ) {
return c . json ({ error: "Admin access required" }, 403 );
}
return c . json ({ error: "Unauthorized" }, 401 );
}
c . set ( "unkey" , data );
await next ();
};
app . delete ( "/api/admin/users/:id" , requireAdmin , ( c ) => {
return c . json ({ deleted: c . req . param ( "id" ) });
});
Context Data
After verification, c.get("unkey") contains:
Field Type Description validbooleanWhether the key passed all checks codestringStatus code (VALID, NOT_FOUND, RATE_LIMITED, etc.) keyIdstringThe key’s unique identifier namestring?Human-readable name of the key metaobject?Custom metadata associated with the key expiresnumber?Unix timestamp (in milliseconds) when the key will expire creditsnumber?Remaining uses (if usage limits set) enabledbooleanWhether the key is enabled rolesstring[]?Named role(s) assigned to the key permissionsstring[]?List of individual permissions identityobject?Identity info if externalId was set ratelimitsobject[]?Rate limit states (if configured)
Deployment
Node.js
npm run build
node dist/index.js
Bun
Cloudflare Workers
For Cloudflare Workers, use wrangler secrets:
npx wrangler secret put UNKEY_ROOT_KEY
Update your worker to read from env:
import { Hono } from "hono" ;
import { unkey } from "@unkey/hono" ;
type Bindings = {
UNKEY_ROOT_KEY : string ;
};
const app = new Hono <{ Bindings : Bindings }>();
app . use ( "/api/*" , async ( c , next ) => {
const handler = unkey ({
rootKey: c . env . UNKEY_ROOT_KEY , // Read from environment
});
return handler ( c , next );
});
export default app ;
Deploy:
Next Steps
Add rate limiting Limit requests per key
Set usage limits Limit requests with credit-based billing
Add permissions Fine-grained access control
Hono SDK Reference Full middleware documentation
Troubleshooting
Getting 401 even with a valid key?
Ensure the key hasn’t expired or been revoked
Verify the header format: Authorization: Bearer YOUR_KEY
Environment variables not loading?
For Node.js: Install dotenv and add import 'dotenv/config' at the top
For Bun: .env is loaded automatically
For Cloudflare Workers: Use wrangler secret or wrangler.toml