chore: Updated package docs 2d55fcf7
Steve · 2025-05-07 01:20 2 file(s) · +464 −2
docs/pages/packages/client.mdx +178 −1
1 +
import { Button } from 'vocs/components'
2 +
1 3
# `client`
2 4
3 -
Coming soon
5 +
bhvr uses Vite + React as its default client side template, as React has become one of the industry defaults with a vast amount of ecosystem support. With that said you can absolutely replace it with your client of choice by [following the guide](). We highly recommend using [Vite](https://vite.dev) as your bundler since it too has lots of ecosystem suppport and is very lightweight.
6 +
7 +
8 +
## Basics
9 +
10 +
Just in case you haven't used Vite + React much here are some basics you may want to keep in mind.
11 +
12 +
### Client Side Only
13 +
14 +
Unlike Next.js, Vite + React is client side only. This means any kind of environment variable used here will be publicly accessible, which is why we have a [`server`](/packages/server) package to keep those secure. This can take some getting used to when building an application and how you might fetch data. A classic way to achieve an "on-load" API call to your server is through a `useEffect`.
15 +
16 +
```tsx App.tsx
17 +
import { useState, useEffect } from 'react';
18 +
19 +
function UserProfile() {
20 +
  const [user, setUser] = useState(null);
21 +
  const [loading, setLoading] = useState(true);
22 +
  const [error, setError] = useState(null);
23 +
24 +
  useEffect(() => {
25 +
    // Define an async function inside useEffect
26 +
    async function fetchUser() {
27 +
      try {
28 +
        setLoading(true);
29 +
        // Make the API call to your server
30 +
        const response = await fetch(`${import.meta.env.SERVER_URL}`);
31 +
32 +
        // Handle non-200 responses
33 +
        if (!response.ok) {
34 +
          throw new Error(`Error: ${response.status}`);
35 +
        }
36 +
37 +
        const data = await response.json();
38 +
        setUser(data);
39 +
        setError(null);
40 +
      } catch (err) {
41 +
        setError(err.message);
42 +
        setUser(null);
43 +
      } finally {
44 +
        setLoading(false);
45 +
      }
46 +
    }
47 +
48 +
    // Call the function
49 +
    fetchUser();
50 +
51 +
    // If needed, you can return a cleanup function
52 +
    return () => {
53 +
      // Any cleanup code (if needed)
54 +
    };
55 +
  }, []); // Empty dependency array means this runs once on mount
56 +
57 +
  if (loading) return <div>Loading...</div>;
58 +
  if (error) return <div>Error: {error}</div>;
59 +
  if (!user) return <div>No user data found</div>;
60 +
61 +
  return (
62 +
    <div>
63 +
      <h2>User Profile</h2>
64 +
      <p>Name: {user.name}</p>
65 +
      <p>Email: {user.email}</p>
66 +
    </div>
67 +
  );
68 +
}
69 +
```
70 +
71 +
### Environment Variables
72 +
73 +
Despite environment variables being public in this setup, they still come in handy for things like working with your local server URL vs your deployed instance. It's best practice to keep your variables in a `.env.local` file with the following format, taking special note that they need to start with `VITE_`.
74 +
75 +
```
76 +
// [!code word:VITE]
77 +
VITE_MY_VAR=value
78 +
```
79 +
80 +
To use them inside your app be sure to use this Vite formatting
81 +
82 +
```typescript
83 +
const variable = import.meta.env.VITE_MY_VAR
84 +
```
85 +
86 +
### Check the Config
87 +
88 +
Vite's config file `vite.config.ts` is worth exploring as it can provide some extra options and plugins.
89 +
90 +
## Styles
91 +
92 +
When creating a new bhvr project you can use the CLI to specify the CSS template you want
93 +
94 +
```bash [terminal]
95 +
bun create bhvr@latest --template default # Classic CSS
96 +
bun create bhvr@latest --template tailwind # Tailwind installed and setup
97 +
bun create bhvr@latest --template shadcn # Tailwind + Shadcn/ui component setup
98 +
```
99 +
100 +
## Routing
101 +
102 +
There are serveral ways to handle routing in your client app, but few some close to [React Router](http://reactrouter.com). Setting it up is quite simple and intuitive.
103 +
104 +
::::steps
105 +
106 +
### Install `react-router`
107 +
108 +
Make sure you're inside the `client` directory and then install `react-router`
109 +
110 +
```bash [terminal]
111 +
bun add react-router
112 +
```
113 +
114 +
### Setup Router
115 +
116 +
You can do this inside `main.tsx` or `App.tsx`, I prefer the latter. All you have to do is import the `BrowserRouter`, `Routes`, then declare your routes with the compnents they go to inside.
117 +
118 +
```tsx App.tsx
119 +
import { BrowserRouter, Routes, Route } from "react-router";
120 +
import Home from "./components/Home";
121 +
122 +
function App() {
123 +
  return (
124 +
    <BrowserRouter>
125 +
      <Routes>
126 +
        <Route path="/" element={<Home />} />
127 +
      </Routes>
128 +
    </BrowserRouter>
129 +
  );
130 +
}
131 +
132 +
export default App
133 +
```
134 +
135 +
### Use Dynamic Routes
136 +
137 +
If you want to have a dynamic route with a path param you can set it up in your `Routes` like so
138 +
139 +
```tsx
140 +
import { BrowserRouter, Routes, Route } from "react-router";
141 +
import Home from "./components/Home";
142 +
import Post from "./components/Post";
143 +
144 +
function App() {
145 +
  return (
146 +
    <BrowserRouter>
147 +
      <Routes>
148 +
        <Route path="/" element={<Home />} />
149 +
        <Route path="/post/:slug" element={<Post />} />
150 +
      </Routes>
151 +
    </BrowserRouter>
152 +
  );
153 +
}
154 +
155 +
export default App
156 +
```
157 +
158 +
Then inside the component you can access those params with `useParams`
159 +
160 +
```typescript
161 +
import { useParams } from "react-router";
162 +
163 +
function Post(){
164 +
  const { slug } = useParams()
165 +
166 +
  render (
167 +
    <>
168 +
      <h1>Post {slug}</h1>
169 +
    </>
170 +
  )
171 +
}
172 +
173 +
export default Post;
174 +
```
175 +
176 +
::::
177 +
178 +
## Deployment
179 +
180 +
<Button href="/deployment/client/orbiter">Deployments Section</Button>
docs/pages/packages/server.mdx +286 −1
1 +
import { Button } from 'vocs/components'
2 +
1 3
# `server`
2 4
3 -
Coming soon
5 +
This package lets us build an API or connect databases and other tools that require a server enviornment. bhvr uses [Hono](https://hono.dev) to power the API as it's simple, easy to use, and has a lot of ecosystem plugins and adoption. It's similar to express but lighter and more modern.
6 +
7 +
:::tip
8 +
Check out the [official Hono documentation](https://hono.dev) for a full reference
9 +
:::
10 +
11 +
## Basics
12 +
13 +
You can declare routes in your API like so
14 +
15 +
```typescript
16 +
import { Hono } from 'hono'
17 +
18 +
const app = new Hono()
19 +
20 +
app.get('/', (c) => {
21 +
  return c.text('Hello Hono!')
22 +
})
23 +
24 +
export default app
25 +
```
26 +
27 +
Hono also makes it easy to add in path parameters
28 +
29 +
```typescript
30 +
app.get('/user/:name', async (c) => {
31 +
  const name = c.req.param('name')
32 +
  // ...
33 +
})
34 +
```
35 +
36 +
or multiple parameters
37 +
38 +
```typescript
39 +
app.get('/posts/:id/comment/:comment_id', async (c) => {
40 +
  const { id, comment_id } = c.req.param()
41 +
  // ...
42 +
})
43 +
```
44 +
45 +
The `(c)` in Hono is the `Context` which has loads of primary features of your API.
46 +
47 +
**Access a Request**
48 +
49 +
```typescript
50 +
app.get('/hello', (c) => {
51 +
  const userAgent = c.req.header('User-Agent')
52 +
  // ...
53 +
})
54 +
```
55 +
56 +
**Return JSON or HTML**
57 +
58 +
```typescript
59 +
app.get('/api', (c) => {
60 +
  return c.json({ message: 'Hello!' })
61 +
})
62 +
```
63 +
64 +
```typescript
65 +
app.get('/', (c) => {
66 +
  return c.html('<h1>Hello! Hono!</h1>')
67 +
})
68 +
```
69 +
70 +
**Access an ENV**
71 +
72 +
```typescript
73 +
// Type definition to make type inference
74 +
type Bindings = {
75 +
  MY_KV: KVNamespace
76 +
}
77 +
78 +
const app = new Hono<{ Bindings: Bindings }>()
79 +
80 +
// Environment object for Cloudflare Workers
81 +
app.get('/', async (c) => {
82 +
  c.env.MY_KV.get('my-key')
83 +
  // ...
84 +
})
85 +
```
86 +
87 +
## RPC
88 +
89 +
One of the unique built in features of Hono is it's RPC. With this enabled you can create a Hono client in your frontend and get automatic type safety without needing to import or export types from `shared`. This is not enabled by default to help keep an unbiased template starter, but when creating a new bhvr project it is easy to enable.
90 +
91 +
```bash [terminal]
92 +
bun create bhvr@latest --rpc
93 +
```
94 +
95 +
This will setup your API with the following code, and the key being the use of `const routes` and exporting the `AppType`
96 +
97 +
```typescript src/index.ts
98 +
import { Hono } from 'hono'
99 +
import { cors } from 'hono/cors'
100 +
import type { ApiResponse } from 'shared/dist'
101 +
102 +
const app = new Hono()
103 +
104 +
app.use(cors())
105 +
106 +
const routes = app.get('/', (c) => { //[!code focus]
107 +
  return c.text('Hello Hono!')
108 +
})
109 +
110 +
.get('/hello', async (c) => {
111 +
112 +
  const data: ApiResponse = {
113 +
    message: "Hello BHVR!",
114 +
    success: true
115 +
  }
116 +
117 +
  return c.json(data, { status: 200 })
118 +
})
119 +
120 +
export type AppType = typeof routes // [!code focus]
121 +
export default app
122 +
```
123 +
124 +
In your `client` code Hono is installed as a dependency, and the `hc` client is imported and initialized. The `AppType` is also used so we can build types with it.
125 +
126 +
```typescript src/App.tsx
127 +
import { useState } from 'react'
128 +
import beaver from './assets/beaver.svg'
129 +
import type { AppType } from 'server' // [!code focus]
130 +
import { hc } from 'hono/client' // [!code focus]
131 +
import './App.css'
132 +
133 +
const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000"
134 +
135 +
const client = hc<AppType>(SERVER_URL); // [!code focus]
136 +
137 +
type ResponseType = Awaited<ReturnType<typeof client.hello.$get>>; // [!code focus]
138 +
139 +
function App() {
140 +
  const [data, setData] = useState<Awaited<ReturnType<ResponseType["json"]>> | undefined>()
141 +
142 +
  async function sendRequest() {
143 +
    try {
144 +
      const res = await client.hello.$get()
145 +
      if (!res.ok) {
146 +
        console.log("Error fetching data")
147 +
        return
148 +
      }
149 +
      const data = await res.json()
150 +
      setData(data)
151 +
    } catch (error) {
152 +
      console.log(error)
153 +
    }
154 +
  }
155 +
156 +
  return (
157 +
    <>
158 +
      {/* JSX markup...*/}
159 +
    </>
160 +
  )
161 +
}
162 +
163 +
export default App
164 +
```
165 +
166 +
## DB Connections
167 +
168 +
There are lots of options out there which can be simple as installing some packages and setting up API keys like [Supabase](https://supabase.com). You can also install ORM clients to handle raw connections like [Drizzle](https://orm.drizzle.team) or [Prisma](https://prisma.io/orm). Since Hono works great with Cloudflare Workers I would also highly recommend checking out [D1](https://developers.cloudflare.com/d1) as it can be easily accessed through the Hono context.
169 +
170 +
```typescript
171 +
import { Hono } from "hono";
172 +
173 +
// This ensures c.env.DB is correctly typed
174 +
type Bindings = {
175 +
  DB: D1Database;
176 +
};
177 +
178 +
const app = new Hono<{ Bindings: Bindings }>();
179 +
180 +
// Accessing D1 is via the c.env.YOUR_BINDING property
181 +
app.get("/query/users/:id", async (c) => {
182 +
  const userId = c.req.param("id");
183 +
  try {
184 +
    let { results } = await c.env.DB.prepare(
185 +
      "SELECT * FROM users WHERE user_id = ?",
186 +
    )
187 +
      .bind(userId)
188 +
      .all();
189 +
    return c.json(results);
190 +
  } catch (e) {
191 +
    return c.json({ err: e.message }, 500);
192 +
  }
193 +
});
194 +
195 +
// Export our Hono app: Hono automatically exports a
196 +
// Workers 'fetch' handler for you
197 +
export default app;
198 +
```
199 +
200 +
## Cloudflare Workers
201 +
202 +
One of the best ways to use Hono is with Cloudflare Workers. They're cheap, pretty easy to use, and can link to other Cloudflare services like KVs or Databases. However there are some differences with how you use Hono if you take the Worker route, so we'll point out some tips here.
203 +
204 +
### Environment Variables
205 +
206 +
Cloudlfare has both public and private variables, and can only be accessed through the Hono Context. This means if you have a function in another file or folder using something like `process.env.MY_SECRET` it's not going to work. Instead you need to put your environemnt names as Bindings to make everything typesafe.
207 +
208 +
```typescript
209 +
type Bindings = {
210 +
  MY_BUCKET: R2Bucket
211 +
  USERNAME: string
212 +
  PASSWORD: string
213 +
}
214 +
215 +
const app = new Hono<{ Bindings: Bindings }>()
216 +
217 +
// Access to environment values
218 +
app.put('/upload/:key', async (c, next) => {
219 +
  const key = c.req.param('key')
220 +
  await c.env.MY_BUCKET.put(key, c.req.body)
221 +
  return c.text(`Put ${key} successfully!`)
222 +
})
223 +
```
224 +
225 +
To set these variables, public ones can be put inside the `wrangler.jsonc` or `wrangler.toml` file.
226 +
227 +
```jsonc
228 +
{
229 +
  "$schema": "node_modules/wrangler/config-schema.json",
230 +
  "name": "server",
231 +
  "main": "src/index.ts",
232 +
  "compatibility_date": "2025-05-07",
233 +
  // "compatibility_flags": [
234 +
  //   "nodejs_compat"
235 +
  // ],
236 +
  "vars": {
237 +
    "MY_VAR": "my-variable"
238 +
  },
239 +
  "kv_namespaces": [
240 +
    {
241 +
      "binding": "MY_KV_NAMESPACE",
242 +
      "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
243 +
    }
244 +
  ],
245 +
  // "r2_buckets": [
246 +
  //   {
247 +
  //     "binding": "MY_BUCKET",
248 +
  //     "bucket_name": "my-bucket"
249 +
  //   }
250 +
  // ],
251 +
  // "d1_databases": [
252 +
  //   {
253 +
  //     "binding": "MY_DB",
254 +
  //     "database_name": "my-database",
255 +
  //     "database_id": ""
256 +
  //   }
257 +
  // ],
258 +
  // "ai": {
259 +
  //   "binding": "AI"
260 +
  // },
261 +
  // "observability": {
262 +
  //   "enabled": true,
263 +
  //   "head_sampling_rate": 1
264 +
  // }
265 +
}
266 +
```
267 +
268 +
Secret variables can be used in a test env by storing them in a `.dev.vars` file
269 +
270 +
```
271 +
MY_SECRET = "SOME_SECRET"
272 +
```
273 +
274 +
To use secret variables for deployment you can use the Cloudflare dashboard or use the Wrangler CLI
275 +
276 +
```bash [terminal]
277 +
bunx wrangler secret put MY_SECRET
278 +
279 +
# Prompt: Enter your secret to have it encrypted on Cloudflare
280 +
```
281 +
282 +
:::tip
283 +
Read the [Hono documentation](https://hono.dev/docs/getting-started/cloudflare-workers) on Workers for more info
284 +
:::
285 +
286 +
## Deployment
287 +
288 +
<Button href="/deployment/server/cloudflare-workers">Deployments Section</Button>