| 1 | import { Button } from "vocs/components"; |
| 2 | |
| 3 | # `server` |
| 4 | |
| 5 | This package lets us build an API or connect databases and other tools that require a server environment. 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 |
| 107 | .get("/", (c) => { |
| 108 | //[!code focus] |
| 109 | return c.text("Hello Hono!"); |
| 110 | }) |
| 111 | |
| 112 | .get("/hello", async (c) => { |
| 113 | const data: ApiResponse = { |
| 114 | message: "Hello BHVR!", |
| 115 | success: true, |
| 116 | }; |
| 117 | |
| 118 | return c.json(data, { status: 200 }); |
| 119 | }); |
| 120 | |
| 121 | export type AppType = typeof routes; // [!code focus] |
| 122 | export default app; |
| 123 | ``` |
| 124 | |
| 125 | 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. |
| 126 | |
| 127 | ```typescript src/App.tsx |
| 128 | import { useState } from 'react' |
| 129 | import beaver from './assets/beaver.svg' |
| 130 | import type { AppType } from 'server' // [!code focus] |
| 131 | import { hc } from 'hono/client' // [!code focus] |
| 132 | import './App.css' |
| 133 | |
| 134 | const SERVER_URL = import.meta.env.VITE_SERVER_URL || "http://localhost:3000" |
| 135 | |
| 136 | const client = hc<AppType>(SERVER_URL); // [!code focus] |
| 137 | |
| 138 | type ResponseType = Awaited<ReturnType<typeof client.hello.$get>>; // [!code focus] |
| 139 | |
| 140 | function App() { |
| 141 | const [data, setData] = useState<Awaited<ReturnType<ResponseType["json"]>> | undefined>() |
| 142 | |
| 143 | async function sendRequest() { |
| 144 | try { |
| 145 | const res = await client.hello.$get() |
| 146 | if (!res.ok) { |
| 147 | console.log("Error fetching data") |
| 148 | return |
| 149 | } |
| 150 | const data = await res.json() |
| 151 | setData(data) |
| 152 | } catch (error) { |
| 153 | console.log(error) |
| 154 | } |
| 155 | } |
| 156 | |
| 157 | return ( |
| 158 | <> |
| 159 | {/* JSX markup...*/} |
| 160 | </> |
| 161 | ) |
| 162 | } |
| 163 | |
| 164 | export default App |
| 165 | ``` |
| 166 | |
| 167 | ## DB Connections |
| 168 | |
| 169 | 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. |
| 170 | |
| 171 | ```typescript |
| 172 | import { Hono } from "hono"; |
| 173 | |
| 174 | // This ensures c.env.DB is correctly typed |
| 175 | type Bindings = { |
| 176 | DB: D1Database; |
| 177 | }; |
| 178 | |
| 179 | const app = new Hono<{ Bindings: Bindings }>(); |
| 180 | |
| 181 | // Accessing D1 is via the c.env.YOUR_BINDING property |
| 182 | app.get("/query/users/:id", async (c) => { |
| 183 | const userId = c.req.param("id"); |
| 184 | try { |
| 185 | let { results } = await c.env.DB.prepare("SELECT * FROM users WHERE user_id = ?") |
| 186 | .bind(userId) |
| 187 | .all(); |
| 188 | return c.json(results); |
| 189 | } catch (e) { |
| 190 | return c.json({ err: e.message }, 500); |
| 191 | } |
| 192 | }); |
| 193 | |
| 194 | // Export our Hono app: Hono automatically exports a |
| 195 | // Workers 'fetch' handler for you |
| 196 | export default app; |
| 197 | ``` |
| 198 | |
| 199 | ## Cloudflare Workers |
| 200 | |
| 201 | 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. |
| 202 | |
| 203 | ### Environment Variables |
| 204 | |
| 205 | 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. |
| 206 | |
| 207 | ```typescript |
| 208 | type Bindings = { |
| 209 | MY_BUCKET: R2Bucket; |
| 210 | USERNAME: string; |
| 211 | PASSWORD: string; |
| 212 | }; |
| 213 | |
| 214 | const app = new Hono<{ Bindings: Bindings }>(); |
| 215 | |
| 216 | // Access to environment values |
| 217 | app.put("/upload/:key", async (c, next) => { |
| 218 | const key = c.req.param("key"); |
| 219 | await c.env.MY_BUCKET.put(key, c.req.body); |
| 220 | return c.text(`Put ${key} successfully!`); |
| 221 | }); |
| 222 | ``` |
| 223 | |
| 224 | To set these variables, public ones can be put inside the `wrangler.jsonc` or `wrangler.toml` file. |
| 225 | |
| 226 | ```jsonc |
| 227 | { |
| 228 | "$schema": "node_modules/wrangler/config-schema.json", |
| 229 | "name": "server", |
| 230 | "main": "src/index.ts", |
| 231 | "compatibility_date": "2025-05-07", |
| 232 | // "compatibility_flags": [ |
| 233 | // "nodejs_compat" |
| 234 | // ], |
| 235 | "vars": { |
| 236 | "MY_VAR": "my-variable", |
| 237 | }, |
| 238 | "kv_namespaces": [ |
| 239 | { |
| 240 | "binding": "MY_KV_NAMESPACE", |
| 241 | "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", |
| 242 | }, |
| 243 | ], |
| 244 | // "r2_buckets": [ |
| 245 | // { |
| 246 | // "binding": "MY_BUCKET", |
| 247 | // "bucket_name": "my-bucket" |
| 248 | // } |
| 249 | // ], |
| 250 | // "d1_databases": [ |
| 251 | // { |
| 252 | // "binding": "MY_DB", |
| 253 | // "database_name": "my-database", |
| 254 | // "database_id": "" |
| 255 | // } |
| 256 | // ], |
| 257 | // "ai": { |
| 258 | // "binding": "AI" |
| 259 | // }, |
| 260 | // "observability": { |
| 261 | // "enabled": true, |
| 262 | // "head_sampling_rate": 1 |
| 263 | // } |
| 264 | } |
| 265 | ``` |
| 266 | |
| 267 | Secret variables can be used in a test env by storing them in a `.dev.vars` file |
| 268 | |
| 269 | ``` |
| 270 | MY_SECRET = "SOME_SECRET" |
| 271 | ``` |
| 272 | |
| 273 | To use secret variables for deployment you can use the Cloudflare dashboard or use the Wrangler CLI |
| 274 | |
| 275 | ```bash [terminal] |
| 276 | bunx wrangler secret put MY_SECRET |
| 277 | |
| 278 | # Prompt: Enter your secret to have it encrypted on Cloudflare |
| 279 | ``` |
| 280 | |
| 281 | :::tip |
| 282 | Read the [Hono documentation](https://hono.dev/docs/getting-started/cloudflare-workers) on Workers for more info |
| 283 | ::: |
| 284 | |
| 285 | ## Deployment |
| 286 | |
| 287 | <Button href="/deployment/server/cloudflare-workers">Deployments Section</Button> |