chore: Added formatting
92bab845
3 file(s) · +132 −108
| 1 | - | import { useState } from 'react' |
|
| 1 | + | import { useState } from "react"; |
|
| 2 | 2 | ||
| 3 | 3 | export function Landing() { |
|
| 4 | - | const [copied, setCopied] = useState(false) |
|
| 4 | + | const [copied, setCopied] = useState(false); |
|
| 5 | 5 | ||
| 6 | + | const copyToClipboard = async () => { |
|
| 7 | + | try { |
|
| 8 | + | await navigator.clipboard.writeText("bun create bhvr@latest"); |
|
| 9 | + | setCopied(true); |
|
| 10 | + | setTimeout(() => setCopied(false), 2000); |
|
| 11 | + | } catch (err) { |
|
| 12 | + | console.error("Failed to copy:", err); |
|
| 13 | + | } |
|
| 14 | + | }; |
|
| 6 | 15 | ||
| 7 | - | const copyToClipboard = async () => { |
|
| 8 | - | try { |
|
| 9 | - | await navigator.clipboard.writeText('bun create bhvr@latest') |
|
| 10 | - | setCopied(true) |
|
| 11 | - | setTimeout(() => setCopied(false), 2000) |
|
| 12 | - | } catch (err) { |
|
| 13 | - | console.error('Failed to copy:', err) |
|
| 14 | - | } |
|
| 15 | - | } |
|
| 16 | - | ||
| 17 | - | return ( |
|
| 18 | - | <main className="flex max-w-lg sm:max-w-2xl mx-auto flex-col items-center justify-start p-4 "> |
|
| 19 | - | <div className="w-full max-w-2xl flex flex-col items-center gap-8"> |
|
| 20 | - | <div className="flex flex-col items-center gap-3"> |
|
| 21 | - | <p className="text-xl text-muted-foreground">Bun + Hono + Vite + React</p> |
|
| 22 | - | <p className="text-center max-w-md">Modern and lightweight stack for the open web</p> |
|
| 23 | - | </div> |
|
| 24 | - | ||
| 25 | - | <div className="w-full"> |
|
| 26 | - | <div className="relative w-full rounded-lg bg-zinc-800 p-4 overflow-hidden"> |
|
| 27 | - | <pre className="text-sm font-mono"> |
|
| 28 | - | <code>bun create bhvr@latest</code> |
|
| 29 | - | </pre> |
|
| 30 | - | <button |
|
| 31 | - | className="absolute top-2 right-2 rounded-md p-2 hover:bg-gray-200 dark:hover:bg-gray-700 focus:outline-none" |
|
| 32 | - | onClick={copyToClipboard} |
|
| 33 | - | > |
|
| 34 | - | {copied ? ( |
|
| 35 | - | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="h-4 w-4"> |
|
| 36 | - | <polyline points="20 6 9 17 4 12"></polyline> |
|
| 37 | - | </svg> |
|
| 38 | - | ) : ( |
|
| 39 | - | <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="h-4 w-4"> |
|
| 40 | - | <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect> |
|
| 41 | - | <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path> |
|
| 42 | - | </svg> |
|
| 43 | - | )} |
|
| 44 | - | </button> |
|
| 45 | - | </div> |
|
| 46 | - | </div> |
|
| 47 | - | </div> |
|
| 16 | + | return ( |
|
| 17 | + | <main className="flex max-w-lg sm:max-w-2xl mx-auto flex-col items-center justify-start p-4 "> |
|
| 18 | + | <div className="w-full max-w-2xl flex flex-col items-center gap-8"> |
|
| 19 | + | <div className="flex flex-col items-center gap-3"> |
|
| 20 | + | <p className="text-xl text-muted-foreground"> |
|
| 21 | + | Bun + Hono + Vite + React |
|
| 22 | + | </p> |
|
| 23 | + | <p className="text-center max-w-md"> |
|
| 24 | + | Modern and lightweight stack for the open web |
|
| 25 | + | </p> |
|
| 26 | + | </div> |
|
| 48 | 27 | ||
| 49 | - | </main> |
|
| 50 | - | ) |
|
| 28 | + | <div className="w-full"> |
|
| 29 | + | <div className="relative w-full rounded-lg bg-zinc-800 p-4 overflow-hidden"> |
|
| 30 | + | <pre className="text-sm font-mono"> |
|
| 31 | + | <code>bun create bhvr@latest</code> |
|
| 32 | + | </pre> |
|
| 33 | + | <button |
|
| 34 | + | className="absolute top-2 right-2 rounded-md p-2 hover:bg-gray-200 dark:hover:bg-gray-700 focus:outline-none" |
|
| 35 | + | onClick={copyToClipboard} |
|
| 36 | + | > |
|
| 37 | + | {copied ? ( |
|
| 38 | + | <svg |
|
| 39 | + | xmlns="http://www.w3.org/2000/svg" |
|
| 40 | + | width="16" |
|
| 41 | + | height="16" |
|
| 42 | + | viewBox="0 0 24 24" |
|
| 43 | + | fill="none" |
|
| 44 | + | stroke="currentColor" |
|
| 45 | + | strokeWidth="2" |
|
| 46 | + | strokeLinecap="round" |
|
| 47 | + | strokeLinejoin="round" |
|
| 48 | + | className="h-4 w-4" |
|
| 49 | + | > |
|
| 50 | + | <polyline points="20 6 9 17 4 12"></polyline> |
|
| 51 | + | </svg> |
|
| 52 | + | ) : ( |
|
| 53 | + | <svg |
|
| 54 | + | xmlns="http://www.w3.org/2000/svg" |
|
| 55 | + | width="16" |
|
| 56 | + | height="16" |
|
| 57 | + | viewBox="0 0 24 24" |
|
| 58 | + | fill="none" |
|
| 59 | + | stroke="currentColor" |
|
| 60 | + | strokeWidth="2" |
|
| 61 | + | strokeLinecap="round" |
|
| 62 | + | strokeLinejoin="round" |
|
| 63 | + | className="h-4 w-4" |
|
| 64 | + | > |
|
| 65 | + | <rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect> |
|
| 66 | + | <path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path> |
|
| 67 | + | </svg> |
|
| 68 | + | )} |
|
| 69 | + | </button> |
|
| 70 | + | </div> |
|
| 71 | + | </div> |
|
| 72 | + | </div> |
|
| 73 | + | </main> |
|
| 74 | + | ); |
|
| 51 | 75 | } |
| 1 | - | import { Button } from 'vocs/components' |
|
| 1 | + | import { Button } from "vocs/components"; |
|
| 2 | 2 | ||
| 3 | 3 | # `server` |
|
| 4 | 4 | ||
| 13 | 13 | You can declare routes in your API like so |
|
| 14 | 14 | ||
| 15 | 15 | ```typescript |
|
| 16 | - | import { Hono } from 'hono' |
|
| 16 | + | import { Hono } from "hono"; |
|
| 17 | 17 | ||
| 18 | - | const app = new Hono() |
|
| 18 | + | const app = new Hono(); |
|
| 19 | 19 | ||
| 20 | - | app.get('/', (c) => { |
|
| 21 | - | return c.text('Hello Hono!') |
|
| 22 | - | }) |
|
| 20 | + | app.get("/", (c) => { |
|
| 21 | + | return c.text("Hello Hono!"); |
|
| 22 | + | }); |
|
| 23 | 23 | ||
| 24 | - | export default app |
|
| 24 | + | export default app; |
|
| 25 | 25 | ``` |
|
| 26 | 26 | ||
| 27 | 27 | Hono also makes it easy to add in path parameters |
|
| 28 | 28 | ||
| 29 | 29 | ```typescript |
|
| 30 | - | app.get('/user/:name', async (c) => { |
|
| 31 | - | const name = c.req.param('name') |
|
| 30 | + | app.get("/user/:name", async (c) => { |
|
| 31 | + | const name = c.req.param("name"); |
|
| 32 | 32 | // ... |
|
| 33 | - | }) |
|
| 33 | + | }); |
|
| 34 | 34 | ``` |
|
| 35 | 35 | ||
| 36 | 36 | or multiple parameters |
|
| 37 | 37 | ||
| 38 | 38 | ```typescript |
|
| 39 | - | app.get('/posts/:id/comment/:comment_id', async (c) => { |
|
| 40 | - | const { id, comment_id } = c.req.param() |
|
| 39 | + | app.get("/posts/:id/comment/:comment_id", async (c) => { |
|
| 40 | + | const { id, comment_id } = c.req.param(); |
|
| 41 | 41 | // ... |
|
| 42 | - | }) |
|
| 42 | + | }); |
|
| 43 | 43 | ``` |
|
| 44 | 44 | ||
| 45 | 45 | The `(c)` in Hono is the `Context` which has loads of primary features of your API. |
|
| 47 | 47 | **Access a Request** |
|
| 48 | 48 | ||
| 49 | 49 | ```typescript |
|
| 50 | - | app.get('/hello', (c) => { |
|
| 51 | - | const userAgent = c.req.header('User-Agent') |
|
| 50 | + | app.get("/hello", (c) => { |
|
| 51 | + | const userAgent = c.req.header("User-Agent"); |
|
| 52 | 52 | // ... |
|
| 53 | - | }) |
|
| 53 | + | }); |
|
| 54 | 54 | ``` |
|
| 55 | 55 | ||
| 56 | 56 | **Return JSON or HTML** |
|
| 57 | 57 | ||
| 58 | 58 | ```typescript |
|
| 59 | - | app.get('/api', (c) => { |
|
| 60 | - | return c.json({ message: 'Hello!' }) |
|
| 61 | - | }) |
|
| 59 | + | app.get("/api", (c) => { |
|
| 60 | + | return c.json({ message: "Hello!" }); |
|
| 61 | + | }); |
|
| 62 | 62 | ``` |
|
| 63 | 63 | ||
| 64 | 64 | ```typescript |
|
| 65 | - | app.get('/', (c) => { |
|
| 66 | - | return c.html('<h1>Hello! Hono!</h1>') |
|
| 67 | - | }) |
|
| 65 | + | app.get("/", (c) => { |
|
| 66 | + | return c.html("<h1>Hello! Hono!</h1>"); |
|
| 67 | + | }); |
|
| 68 | 68 | ``` |
|
| 69 | 69 | ||
| 70 | 70 | **Access an ENV** |
|
| 72 | 72 | ```typescript |
|
| 73 | 73 | // Type definition to make type inference |
|
| 74 | 74 | type Bindings = { |
|
| 75 | - | MY_KV: KVNamespace |
|
| 76 | - | } |
|
| 75 | + | MY_KV: KVNamespace; |
|
| 76 | + | }; |
|
| 77 | 77 | ||
| 78 | - | const app = new Hono<{ Bindings: Bindings }>() |
|
| 78 | + | const app = new Hono<{ Bindings: Bindings }>(); |
|
| 79 | 79 | ||
| 80 | 80 | // Environment object for Cloudflare Workers |
|
| 81 | - | app.get('/', async (c) => { |
|
| 82 | - | c.env.MY_KV.get('my-key') |
|
| 81 | + | app.get("/", async (c) => { |
|
| 82 | + | c.env.MY_KV.get("my-key"); |
|
| 83 | 83 | // ... |
|
| 84 | - | }) |
|
| 84 | + | }); |
|
| 85 | 85 | ``` |
|
| 86 | 86 | ||
| 87 | 87 | ## RPC |
|
| 95 | 95 | This will setup your API with the following code, and the key being the use of `const routes` and exporting the `AppType` |
|
| 96 | 96 | ||
| 97 | 97 | ```typescript src/index.ts |
|
| 98 | - | import { Hono } from 'hono' |
|
| 99 | - | import { cors } from 'hono/cors' |
|
| 100 | - | import type { ApiResponse } from 'shared/dist' |
|
| 98 | + | import { Hono } from "hono"; |
|
| 99 | + | import { cors } from "hono/cors"; |
|
| 100 | + | import type { ApiResponse } from "shared/dist"; |
|
| 101 | 101 | ||
| 102 | - | const app = new Hono() |
|
| 102 | + | const app = new Hono(); |
|
| 103 | 103 | ||
| 104 | - | app.use(cors()) |
|
| 104 | + | app.use(cors()); |
|
| 105 | 105 | ||
| 106 | - | const routes = app.get('/', (c) => { //[!code focus] |
|
| 107 | - | return c.text('Hello Hono!') |
|
| 108 | - | }) |
|
| 106 | + | const routes = app |
|
| 107 | + | .get("/", (c) => { |
|
| 108 | + | //[!code focus] |
|
| 109 | + | return c.text("Hello Hono!"); |
|
| 110 | + | }) |
|
| 109 | 111 | ||
| 110 | - | .get('/hello', async (c) => { |
|
| 112 | + | .get("/hello", async (c) => { |
|
| 113 | + | const data: ApiResponse = { |
|
| 114 | + | message: "Hello BHVR!", |
|
| 115 | + | success: true, |
|
| 116 | + | }; |
|
| 111 | 117 | ||
| 112 | - | const data: ApiResponse = { |
|
| 113 | - | message: "Hello BHVR!", |
|
| 114 | - | success: true |
|
| 115 | - | } |
|
| 118 | + | return c.json(data, { status: 200 }); |
|
| 119 | + | }); |
|
| 116 | 120 | ||
| 117 | - | return c.json(data, { status: 200 }) |
|
| 118 | - | }) |
|
| 119 | - | ||
| 120 | - | export type AppType = typeof routes // [!code focus] |
|
| 121 | - | export default app |
|
| 121 | + | export type AppType = typeof routes; // [!code focus] |
|
| 122 | + | export default app; |
|
| 122 | 123 | ``` |
|
| 123 | 124 | ||
| 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. |
|
| 181 | 182 | app.get("/query/users/:id", async (c) => { |
|
| 182 | 183 | const userId = c.req.param("id"); |
|
| 183 | 184 | try { |
|
| 184 | - | let { results } = await c.env.DB.prepare( |
|
| 185 | - | "SELECT * FROM users WHERE user_id = ?", |
|
| 186 | - | ) |
|
| 185 | + | let { results } = await c.env.DB.prepare("SELECT * FROM users WHERE user_id = ?") |
|
| 187 | 186 | .bind(userId) |
|
| 188 | 187 | .all(); |
|
| 189 | 188 | return c.json(results); |
|
| 207 | 206 | ||
| 208 | 207 | ```typescript |
|
| 209 | 208 | type Bindings = { |
|
| 210 | - | MY_BUCKET: R2Bucket |
|
| 211 | - | USERNAME: string |
|
| 212 | - | PASSWORD: string |
|
| 213 | - | } |
|
| 209 | + | MY_BUCKET: R2Bucket; |
|
| 210 | + | USERNAME: string; |
|
| 211 | + | PASSWORD: string; |
|
| 212 | + | }; |
|
| 214 | 213 | ||
| 215 | - | const app = new Hono<{ Bindings: Bindings }>() |
|
| 214 | + | const app = new Hono<{ Bindings: Bindings }>(); |
|
| 216 | 215 | ||
| 217 | 216 | // 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 | - | }) |
|
| 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 | + | }); |
|
| 223 | 222 | ``` |
|
| 224 | 223 | ||
| 225 | 224 | To set these variables, public ones can be put inside the `wrangler.jsonc` or `wrangler.toml` file. |
|
| 234 | 233 | // "nodejs_compat" |
|
| 235 | 234 | // ], |
|
| 236 | 235 | "vars": { |
|
| 237 | - | "MY_VAR": "my-variable" |
|
| 236 | + | "MY_VAR": "my-variable", |
|
| 238 | 237 | }, |
|
| 239 | 238 | "kv_namespaces": [ |
|
| 240 | 239 | { |
|
| 241 | 240 | "binding": "MY_KV_NAMESPACE", |
|
| 242 | - | "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" |
|
| 243 | - | } |
|
| 241 | + | "id": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", |
|
| 242 | + | }, |
|
| 244 | 243 | ], |
|
| 245 | 244 | // "r2_buckets": [ |
|
| 246 | 245 | // { |
|
| 6 | 6 | "scripts": { |
|
| 7 | 7 | "dev": "vocs dev", |
|
| 8 | 8 | "build": "vocs build -o ../dist", |
|
| 9 | - | "preview": "vocs preview" |
|
| 9 | + | "preview": "vocs preview", |
|
| 10 | + | "format": "bunx biome format --write ./docs" |
|
| 10 | 11 | }, |
|
| 11 | 12 | "dependencies": { |
|
| 12 | 13 | "react": "latest", |