@gnosticdev/hono-actions
v2.0.11
Published
Define server actions with built-in validation, error handling, and a pre-built hono client for calling the routes.
Maintainers
Readme
Astro Actions with Hono
Define server actions with built-in validation, error handling, and a pre-built hono client for calling the routes.
Installation
# with astro cli
astro add @gnosticdev/hono-actions
# package manager (npm, pnpm, bun, etc.)
bun add @gnosticdev/hono-actionsRequirements
This package requires:
astro: ^5.13.0
[!IMPORTANT] For typescript, make sure you set
strictNullCheckstotrue, or extendastro/tsconfigs/strict(est)in yourtsconfig.json
Supported Adapters
This integration works with all supported Astro adapters:
@astrojs/cloudflare@astrojs/node(standalone mode only)@astrojs/vercel@astrojs/netlify
Setup
1. Add the integration to your Astro config
The integration works with all Astro adapters. Here are examples for each:
Cloudflare/Vercel/Netlify Adapter
// astro.config.ts
import { defineConfig } from 'astro/config'
import cloudflare from '@astrojs/cloudflare'
import honoActions from '@gnosticdev/hono-actions'
export default defineConfig({
output: 'server',
adapter: cloudflare() // vercel() or netlify(),
integrations: [
honoActions({
basePath: '/api', // Optional: default is '/api'
actionsPath: 'src/server/actions.ts' // Optional: custom path to your actions file
})
]
})Node.js Adapter
Node.js adapter only supports the 'standalone' mode.
// astro.config.ts
import { defineConfig } from 'astro/config'
import node from '@astrojs/node'
import honoActions from '@gnosticdev/hono-actions'
export default defineConfig({
output: 'server',
adapter: node({
mode: 'standalone'
}),
integrations: [
honoActions()
]
})2. Create your actions file
Create a file at one of these locations, or use the actionsPath option to specify a custom path:
src/server/actions.tssrc/hono/actions.tssrc/hono/index.tssrc/hono.tssrc/hono-actions.ts
Usage
// src/hono.ts (or any of the supported locations above)
import { defineHonoAction type HonoEnv } from '@gnosticdev/hono-actions/actions'
import { z } from 'astro/zod'
import { Hono } from 'hono'
export const myAction = defineHonoAction({
schema: z.object({
name: z.string()
}),
handler: async (input, ctx) => {
// `input` is automatically typed from the schema
// `ctx` is a strongly-typed Hono Context with your `HonoEnv`
return { message: `Hello ${input.name}!` }
}
})
// Define another POST action
export const anotherAction = defineHonoAction({
schema: z.object({ name2: z.string() }),
handler: async (input, ctx) => {
return {
message2: `Hello ${input.name2}!`
}
}
})
// Optional: Define an action without a schema (accepts any JSON)
export const noSchemaAction = defineHonoAction({
handler: async (input, ctx) => {
if (!('name' in input)) {
throw new HonoActionError({
message: 'Name is required',
code: 'INPUT_VALIDATION_ERROR'
})
}
return { message: `Hello ${String((input as any).name)}!` }
}
})
// You can also define standard Hono routes (GET/PATCH/etc.), not just POST actions.
// This is useful where standard Astro actions are POST-only.
const app = new Hono<HonoEnv>()
const getRoute = app.get('/', (c) => c.json({ message: 'Hi from a get route' }))
// Export all actions and routes in a single `honoActions` object.
// Each key becomes the route name under your basePath, e.g.:
// - POST /api/myAction
// - POST /api/anotherAction
// - GET /api/getRoute
export const honoActions = {
myAction,
anotherAction,
noSchemaAction,
getRoute
}3. Use actions in your Astro components or pages
// src/pages/example.astro or any .astro file
---
import { honoClient, parseResponse } from '@gnosticdev/hono-actions/client'
// Call a POST action
const { data: actionRes } = await parseResponse(
await honoClient.api.myAction.$post({ json: { name: 'John' } })
)
// Call a GET route
const { message } = await parseResponse(
await honoClient.api.getRoute.$get()
)
---
<div>
{actionRes && <p>{actionRes.message}</p>}
<p>{message}</p>
</div>4. Use in client-side JavaScript
// In a client-side script or component
import { honoClient } from '@gnosticdev/hono-actions/client'
import type { DetailedError } from '@gnosticdev/hono-actions/client'
// Make requests from the browser
const handleSubmit = async (formData: FormData) => {
const { data, error } = await parseResponse(await honoClient.api.anotherAction.$post({
json: {
name2: formData.get('name') as string
}
}).catch((err: DetailedError) => {
console.error('Error:', err)
return {
data: null,
error: err
}
})
if (error){
throw new Error(`Action failed: ${error.message}`)
}
// type safe access to the data
console.log('my name is ', data.name2)
}Augmenting Type Interfaces
When using this library, you may need to augment the type interfaces to add custom types for your environment bindings, Hono context variables, or Astro locals. This is especially important when using the Cloudflare adapter.
Cloudflare Runtime Types
When using the @astrojs/cloudflare adapter, the library automatically exports Cloudflare runtime types. However, it assumes that Cloudflare types have been generated by Wrangler via the wrangler types command, which creates an Env interface that is automatically added to HonoEnv['Bindings'].
To generate Cloudflare types:
bunx --bun wrangler typesThis creates a worker-configuration.d.ts file (or similar) containing the Env interface based on your wrangler.jsonc configuration.
Extending HonoEnv Types
To add additional types to your Hono environment, create a type declaration file (e.g., src/env.d.ts) and augment the module:
// src/env.d.ts
import '@gnosticdev/hono-actions/actions'
// Augmenting the actions types for use with cloudflare adapter
declare module '@gnosticdev/hono-actions/actions' {
// 1) Extend existing Bindings, with Env from worker-configuration.d.ts
interface Bindings extends Env {
anotherVar: string
}
// 2) Add Variables available on `ctx.var.db` or `ctx.var.db.get('randomKey')` in hono actions
interface HonoEnv {
Variables: {
db: Map<string, any>
}
Bindings: Bindings
}
}
// Extend Astro Locals if you want to use in middleware
// need to add this to global scope bc we have an import in the file
declare global {
type Runtime = import('@astrojs/cloudflare').Runtime<Env>
declare namespace App {
interface Locals extends Runtime {
db: Map<string, any> // this will now be available on both `ctx.var.db` and `Astro.locals.db`
}
}
}
Now in your actions, you'll have full type safety:
// src/hono.ts
import { defineHonoAction, type HonoEnv } from '@gnosticdev/hono-actions/actions'
export const myAction = defineHonoAction({
handler: async (input, ctx) => {
// ctx.env has type: Bindings (includes Env + your custom bindings)
const kv = ctx.env.CUSTOM_KV
// ctx.var has type: HonoEnv['Variables']
const user = kv.get('user')
return { success: true }
}
})And in your Astro pages:
// src/pages/index.astro
---
// Astro.locals has type: App.Locals
const user = Astro.locals.user
---Package Structure
This package provides these entry points:
@gnosticdev/hono-actions/actions: Action definition utilities (defineHonoAction,HonoActionError,HonoEnv(for cloudflare usage))- Used in your actions file(s)
@gnosticdev/hono-actions/client: Pre-built Hono client and helpers (honoClient,parseResponse)- Safe for browser and server environments
@gnosticdev/hono-actions: Astro integration- Uses Node.js built-ins (fs, path)
- Only used in
astro.config.ts
Configuration Options
The integration accepts the following options:
basePath(optional): The base path for your API routes. Default:'/api'actionsPath(optional): Custom path to your actions file if not using auto-discovery
Features
- ✅ Type-safe: Full TypeScript support with automatic type inference
- ✅ Validation: Built-in request validation using Zod schemas
- ✅ Error handling: Custom error types and automatic error responses
- ✅ Auto-discovery: Automatically finds your actions file
- ✅ Client generation: Pre-built client with full type safety
- ✅ Development: Hot reload support during development
- ✅ Flexible routing: Define standard Hono routes (GET/PATCH/etc.) alongside POST actions
Troubleshooting
Actions not found
If you get an error that no actions were found, make sure:
- Your actions file is in one of the supported locations
- You export a
honoActionsobject containing your actions and any Hono routes - The file path matches the
actionsPathoption if you specified one
Type errors
If you're getting TypeScript errors:
- Make sure all peer dependencies are installed
- Run
astro syncto regenerate types - Restart your TypeScript server in your editor
Module resolution errors
If you get module resolution errors during development:
- Try clearing your node_modules and reinstalling
- Make sure you're using compatible versions of the peer dependencies
Advanced Usage
Using AsyncLocalStorage with Custom Hono Instances
For advanced use cases involving request-scoped data and custom Hono instances, see docs/async_hooks.md.
License
MIT
