How to use Astro with Hono


A photo of myself sat on the green cliffs of Cornwall in the UK with the ocean & sky in the background.

Astro is the future of web frameworks. At least for me it is. It’s been so nice to use & feels like the do it all framework to the point I want to use it for everything and anything.

However, that being said, for building APIs I am yet to find something as nice to use as Hono. It’s simple, runs anywhere & has a very basic but usable RPC system similar to tRPC, but with less slow types.

So, with that, why not learn how to combine the 2 so you can have the best of both worlds?

Setting up Astro

First things first, you need to set up a new Astro project. You can do this by running the following command:

npm create astro@latest

This will walk you through a very fancy CLI guide to set up your project.

Once you have done that, you can run the following command to start your project:

npm run dev

With this up and running you can visit http://localhost:4321 to see your new Astro project.

Setting up Hono

Next, you will of course need to set up a basic Hono app.

To get started you will need to install Hono as a dependency. This can be done with the following command:

npm install hono

With Hono installed you’ll need to create a new endpoint in Astro to point all requests to a Hono instance.

To do this create a new catch-all route in your src/pages/ directory. Something like: src/pages/api/[...path].ts.

With this file you can create a new basic Hono instance like so:

src/pages/api/[...path].ts
import { Hono } from 'hono';
 
const app = new Hono().basePath('/api/');

Make note of the .basePath(...) part here, it’s important. This tells Hono that all incoming requests should be prefixed with this path. In our case since we’re using Astro, this will need to match the file structure of your Astro project. For example if your endpoint is in src/pages/foo/bar/[...path].ts then you should use /foo/bar/ as the base path.

Now, last up to finish setting up Hono we’ll add a basic endpoint to retrieve some mock data.

src/pages/api/[...path].ts
import { Hono } from 'hono';
 
const app = new Hono()
	.basePath('/api/')
	.get('/posts', async (c) => {
		return {
			posts: [
				{ id: 1, title: 'Hello World' },
				{ id: 2, title: 'Goodbye World' },
			],
		};
	});

Binding Hono to Astro

With a basic Hono instance set up, we can now bind this to Astro.

Astro normally expects a function to be exported with the name matching the HTTP method it is expected to handle. Such as GET will handle GET requests, POST will handle POST requests etc.

Now, in our case we need to redirect all requests to Hono. This can be done by creating a new ALL export. So let’s create a basic one of these that just returns some text for now:

src/pages/api/[...path].ts
import { Hono } from 'hono';
 
import type { APIRoute } from 'astro';
 
// ...
 
export const ALL: APIRoute = () => new Response('Hello World');

Next up we can then access the context function parameter to access the incoming request & pass it to Hono’s fetch method.

src/pages/api/[...path].ts
import { Hono } from 'hono';
 
import type { APIRoute } from 'astro';
 
// ...
 
export const ALL: APIRoute = (context) => app.fetch(context.request);

And just like that you can now visit http://localhost:4321/api/posts to see your mock data being returned from Hono.

Add a typed RPC client

Now, for the final part of getting this whole thing working seemlessly. We can make it so when we want to make requests to this API you get typed request options like query parameters, headers etc, along with a fully typed response.

You can read more about how this all works in the Hono documentation.

To set this up is faily easy as all you will need to do export the type of your Hono instance & pass it to the special fetch client that Hono offers called hc.

First up, export the type of your Hono instance:

src/pages/api/[...path].ts
import { Hono } from 'hono';
 
import type { APIRoute } from 'astro';
 
// ...
 
export type App = typeof app;

Now, to actually use this type we can create a basic Astro page with a client-side script to make a fetch request using the Hono client:

src/pages/index.astro
<script>
import { hc } from 'hono/client';
 
import type { App } from './api/[...path].ts';
 
const client = hc<App>(window.location.href);
 
const response = await client.posts.$get();
const json = await response.json();
 
console.log(json);
// { posts: [...] }
</script>

Conclusion

And you’re done! You now have a basic Astro project set up using Hono as your API. With the added bonus of being able to make typed requests & get fully-typed responses.

I encourage more people to experiment & try out Hono with more projects because it is a truly amazing HTTP framework that can do so much more than many people realise. So much so that I am using it for all of my backend related projects & plan on creating some more posts on using it in future.

For now though, keep building & have fun!