3.5 KiB
3.5 KiB
Eden Treaty
e2e type safe RPC client for share type from backend to frontend.
What It Is
Type-safe object representation for Elysia server. Auto-completion + error handling.
Installation
bun add @elysiajs/eden
bun add -d elysia
Export Elysia server type:
const app = new Elysia()
.get('/', () => 'Hi Elysia')
.get('/id/:id', ({ params: { id } }) => id)
.post('/mirror', ({ body }) => body, {
body: t.Object({
id: t.Number(),
name: t.String()
})
})
.listen(3000)
export type App = typeof app
Consume on client side:
import { treaty } from '@elysiajs/eden'
import type { App } from './server'
const client = treaty<App>('localhost:3000')
// response: Hi Elysia
const { data: index } = await client.get()
// response: 1895
const { data: id } = await client.id({ id: 1895 }).get()
// response: { id: 1895, name: 'Skadi' }
const { data: nendoroid } = await client.mirror.post({
id: 1895,
name: 'Skadi'
})
Common Errors & Fixes
- Strict mode: Enable in tsconfig
- Version mismatch:
npm why elysia- must match server/client - TypeScript: Min 5.0
- Method chaining: Required on server
- Bun types:
bun add -d @types/bunif using Bun APIs - Path alias: Must resolve same on frontend/backend
Monorepo Path Alias
Must resolve to same file on frontend/backend
// tsconfig.json at root
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@frontend/*": ["./apps/frontend/src/*"],
"@backend/*": ["./apps/backend/src/*"]
}
}
}
Syntax Mapping
| Path | Method | Treaty |
|---|---|---|
| / | GET | .get() |
| /hi | GET | .hi.get() |
| /deep/nested | POST | .deep.nested.post() |
| /item/:name | GET | .item({ name: 'x' }).get() |
Parameters
With body (POST/PUT/PATCH/DELETE):
.user.post(
{ name: 'Elysia' }, // body
{ headers: {}, query: {}, fetch: {} } // optional
)
No body (GET/HEAD):
.hello.get({ headers: {}, query: {}, fetch: {} })
Empty body with query/headers:
.user.post(null, { query: { name: 'Ely' } })
Fetch options:
.hello.get({ fetch: { signal: controller.signal } })
File upload:
// Accepts: File | File[] | FileList | Blob
.image.post({
title: 'Title',
image: fileInput.files!
})
Response
const { data, error, response, status, headers } = await api.user.post({ name: 'x' })
if (error) {
switch (error.status) {
case 400: throw error.value
default: throw error.value
}
}
// data unwrapped after error handling
return data
status >= 300 → data = null, error has value
Stream/SSE
Interpreted as AsyncGenerator:
const { data, error } = await treaty(app).ok.get()
if (error) throw error
for await (const chunk of data) console.log(chunk)
Utility Types
import { Treaty } from '@elysiajs/eden'
type UserData = Treaty.Data<typeof api.user.post>
type UserError = Treaty.Error<typeof api.user.post>
WebSocket
const chat = api.chat.subscribe()
chat.subscribe((message) => console.log('got', message))
chat.on('open', () => chat.send('hello'))
// Native access: chat.raw
.subscribe() accepts same params as get/head