Hono RPC And TypeScript Project References

I've been working on a full-stack application where the backend is separate from the frontend. The project structure looks like this:

๐Ÿ“ Project Root
โ”œโ”€โ”€ ๐Ÿ“„ .env
โ”œโ”€โ”€ ๐Ÿ“ .git
โ”œโ”€โ”€ ๐Ÿ“„ .gitignore
โ”œโ”€โ”€ ๐Ÿ“ .vscode
โ”œโ”€โ”€ ๐Ÿ“„ README.md
โ”œโ”€โ”€ ๐Ÿ“„ bun.lockb
โ”œโ”€โ”€ ๐Ÿ“„ compose.yml
โ”œโ”€โ”€ ๐Ÿ“„ eslint.config.mjs
โ”œโ”€โ”€ ๐Ÿ“ frontend
โ”œโ”€โ”€ ๐Ÿ“ node_modules
โ”œโ”€โ”€ ๐Ÿ“„ package.json
โ”œโ”€โ”€ ๐Ÿ“ prisma
โ”œโ”€โ”€ ๐Ÿ“ scripts
โ”œโ”€โ”€ ๐Ÿ“ server
โ”œโ”€โ”€ ๐Ÿ“ shared
โ””โ”€โ”€ ๐Ÿ“„ tsconfig.json

The client code is in the "frontend" code, while the server code is in the root directory. I opted for Bun and Hono to build the backend and React with Vite for the frontend.

I like Hono for multiple reasons. One is that its RPC feature allows you to share the API specifications between the server and the client. That means you have a type-safe API and autocomplete functionality.

Once the Hono server is ready, you can create the client on the frontend and pass the type representing your API spec:

import { hc } from 'hono/client';
import { AppType } from '@/server/app'

const client = hc<AppType>('http://localhost:1234/')

You can then use the client to interact with your server. If you hover over the client constant, you can see the available endpoints. Also, if you type client., it'll start suggesting endpoint paths and methods.

> You can check Hono's documentation for more information.

The Problem - AppType Has Type "any"

That's all great when it works. However, in project structures like the one from this article, you may run into issues when trying to import the AppType type from the server.

As the image shows, the IDE says that AppType has the type "any." This means there is no type-safety or auto-completion functionality.

Even though the import is correct, it doesn't work. So, how can we fix that?

The Solution - TypeScript Project References

The solution to make this work is to update the two tsconfig.json files. In the root tsconfig.json file, add:

{
  "compilerOptions": {
    "composite": true,
    ...
  }
}

Then, add the following property to the tsconfig.json file from the "frontend" directory:

{
  "compilerOptions": {
    ....
  },
  "references": [
    ...
    {
      "path": "../tsconfig.json"
    }
  ],
  "files": []
}

If we hover over the AppType after making these two changes, you can see that it works properly.

But how and why does it work now? It works because the TypeScript Project References allow multiple TypeScript projects to "cooperate".

"Cooperate" means that one TypeScript project can access and use code from another TypeScript project. In this case, I import code from the backend on the frontend (AppType).

The referenced project needs to have the composite flag set to true in tsconfig.json. And the project that accesses code from the referenced project needs to specify the path to that project in its tsconfig.json:

....
"references": [
    ...
    {
      "path": "../tsconfig.json"
    }
  ],
....

In simpler terms:

  • backend's tsconfig file - enable the composite flag here
  • frontend's tsconfig - use the references property here

After making the changes, restart the TypeScript server, and it should work properly.

> You can check TypeScript's documentation for more information about the project references. There's also this helpful video from Basarat.

Optional: Same Hono Version on the Client and Server

If you get further errors after setting up the project references, make sure that you use the same Hono version on both the server and the client.

In my case, I had version ^4.6.12 on the backend and ^4.6.14 on the frontend, which caused issues with the Hono client.

Optional: Better Project Structure

The current project structure, with the server in the root directory, is not the best choice. As a result, I split the app into 2 separate folders:

  • client
  • server

Support this blog ๐Ÿงก

If you like this content and it helped you, please consider supporting this blog. This helps me create more free content and keep this blog alive.

Become a supporter