Typed fetch with Sveltekit and Hono using RPC
Tags: svelte, sveltekit, typescript, webdev
Published: 2023-08-19 20:03:16

## What is Hono?
[Hono](https://hono.dev/) is a fast and simple web framework that works in edge environments like cloudflare workers, vercel edge etc. Its API is similar to that of express js, so if you have worked with the express in the past, you can be productive with Hono from day one. If you are writing APIs with Hono for the edge, many of the node APIs like "fs" will not be available in Hono since the edge runtimes don't support them (not completely, atleast).

## What is RPC feature and how is it beneficial?
Typically, to make an API call from the frontend, you need to specify:

1. The base URL + the path of the handler function
2. The request parameters
3. The authorization header

These 3 things are specified in the API call according to the specification shared by the API provider. RPC feature handles exactly this. It allows sharing the API specification between the server and the client, such that, while making an API call to the Hono server, the client is already aware about the remote function path, required and optional request params and the headers required for a successful call.

## How to use the RPC feature?
A simple API route in Hono looks like this:

```
import { Hono } from "hono";

const app = new Hono();

app.get("/", (c) => c.json({ message: "Hello World" }));
app.get("/hello/:name", (c) =>
   c.json({ message: `Hello ${c.req.param("name")}` })
);

export default app;
```
This will return json response to the client but the client will have no specification about the path parameter that the routr `/hello/:name` expects and what is the shape of the response it returns. For the client, to have this specification, we will have to make a few changes to this API route:

```
import { Hono } from "hono";

const app = new Hono();

const router = app
  .get("/", (c) => c.jsonT({ message: "Hello World" }))
  .get("/hello/:name", (c) =>
    c.jsonT({ message: `Hello ${c.req.param("name")}` })
  );

export default app;

export type AppType = typeof router;
```
Here instead of the `.json` response, we return the typed json response `.jsonT` and we also return the type of the router.

On the client side we can access these routes like this:

```
import { AppType } from '.'
import { hc } from 'hono/client'

const client = hc<AppType>('http://localhost:8787/')
```
Here `hc` is the function to create a typed client of the Hono server. We pass the `AppType`, the type of the router returned from the Hono server as generics.

> Note how `AppType` is imported here from the server. This is because both the server and the client are a part of one **monorepo**. Without them being a part of the same monorepo, this is not possible.

Now, we can call our api routes from the client like this

```
const res = await client.hello[':name'].$get({
  param: {
    name: 'hono',
  },
  query: {},
})
```
The client here is fully typed and will give intellisense about the parameters and response.

## Separating routes in different files
In large backend apps, it is quite common to have multiple routes/controllers in separate files. For example, a file named `book.ts` may contain all the endpoints related to books and a separate file named `author.ts` may contain all the endpoints related to the authors. In this case, to share the API specifications using RPC with the client, we can do this:

```
// book.ts
const book = new Hono()
export const bookRoute = book.get('/', (c) => {
  return c.jsonT({
    books: ['foo'],
  })
})

// author.ts
const author = new Hono()
export const authorRoute = author.get('/', (c) => {
  return c.jsonT({
    authors: ['bar'],
  })
})

// main.ts
const app = new Hono()
const appRoute = app.route('/book', bookRoute).route('/author', authorRoute)
```

This way, we can share the complete API specification with our frontend app and make sure the API endpoints are typed and the request and response are properly handled.

This was one of the many lessons I learned in the process of migrating my SaaS [StoreBud](https://www.mystorebud.com/) from AWS to Cloudflare workers. I will keep sharing as and when I learn something new.