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.
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 thecomposite
flag here - frontend's
tsconfig
- use thereferences
property here
After making the changes, restart the TypeScript server, and it should work properly.
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