Eden (@elysia/eden)

repository·main·Indexed 43 days ago

https://github.com/elysiajs/eden

A fully type-safe client for Elysia applications that allows developers to consume server routes with TypeScript autocompletion by sharing the server's type definition. It provides two primary clients: edenTreaty, a proxy-based client for seamless integration, and edenFetch, a lightweight fetch-based client. Version 1.4.10 includes support for WebSocket communication via EdenWS, automatic FormData/File upload handling, and customizable error handling using EdenFetchError.

Tokens
8K
Snippets
30
Records
35
Agent score
55%

What's inside Eden

  1. Use dynamic path parameters in EdenTreaty

    main

    EdenTreaty automatically maps Elysia route parameters (prefixed with :) to function arguments.

    • Required Parameters: If a route is defined as /:id, the client will require an object containing id as a string or number.
    • Optional Parameters: If a route is defined as /:id?, the parameter is treated as optional.

    When calling these routes, the path parameters are passed as the first argument to the generated function, followed by the body (if applicable) and options (for headers/fetch config).

    // If Elysia route is: app.get('/user/:id', ...)
    // EdenTreaty usage:
    const { data } = await client.api.user[':id']({ id: '123' });
    
    // If Elysia route is: app.get('/user/:id?', ...)
    // EdenTreaty usage:
    const { data } = await client.api.user[':id']({ id: '123' });
    const { data } = await client.api.user[':id']({}); // id is optional
  2. Handle FormData and File uploads in EdenTreaty

    main

    EdenTreaty automatically detects if your request body contains File or FileList objects (or is a plain object containing them). If it does, it automatically converts the request into a FormData object.

    This works in both browser and server environments (using Blob on the server). It supports:

    • Single File objects.
    • FileList objects.
    • Arrays containing File objects.

    When FormData is used, the Content-Type header is managed automatically to ensure the boundary is correctly set.

    // If the server expects multipart/form-data
    const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement
    const file = fileInput.files?.[0]
    
    if (file) {
        await client.upload.post({
            file, // Automatically converted to FormData
            description: 'My profile picture'
        })
    }
  3. Automatic content-type handling in edenFetch

    main

    The edenFetch client automatically manages Content-Type headers and body serialization:

    • JSON: If the body is a plain object and no content-type is set, it automatically calls JSON.stringify(body) and sets the header to application/json.
    • FormData/URLSearchParams: If the body is an instance of FormData or URLSearchParams, it is sent as-is without manual stringification.
    • Query Parameters: The query object is automatically converted into a URL search string (e.g., ?key=value). Null or undefined values in the query object are filtered out before transmission.
  4. Handle WebSocket connections with .subscribe()

    main

    Eden Treaty supports WebSocket connections through the .subscribe() method. When .subscribe() is called on a path, it returns an EdenWS instance instead of a standard fetch promise. The protocol (WS vs WSS) is automatically determined based on the base domain's protocol.

    // If your Elysia app has a websocket route at /ws
    const ws = client.ws.subscribe()
    
    ws.send('hello')
    ws.addEventListener('message', (event) => {
      console.log(event.data)
    })
  5. Understand the TreatyResponse structure

    main

    Every request made via EdenTreaty returns a TreatyResponse. This object is a discriminated union that handles both successful and error responses based on the HTTP status code.

    Success Response

    When the status code is one of the SuccessCodes (e.g., 200, 201, 204), the object contains:

    • data: The parsed response body (automatically unwrapped from ELYSIA_FORM_DATA if applicable).
    • error: null.
    • response: The raw Response object.
    • status: The numeric HTTP status code.
    • headers: The response headers.

    Error Response

    When the status code is not a success code, the object contains:

    • data: null.
    • error: An object containing the error details. If the server defined specific error shapes for certain status codes, error will contain those shapes (e.g., { status: 404, value: 'Not Found' }).
    • response: The raw Response object.
    • status: The numeric HTTP status code.
    • headers: The response headers.
    const { data, error, status } = await client.api.user.get();
    
    if (error) {
      console.error(`Error ${status}:`, error);
      return;
    }
    
    console.log(data);
  6. Understand the edenFetch response object

    main

    Every request made via the edenFetch client returns a promise that resolves to a response object. This object follows a consistent shape regardless of whether the request succeeded or failed.

    Response Shape:

    • data: The parsed response body (e.g., JSON, ArrayBuffer, or FormData). Returns null if an error occurred.
    • error: An EdenFetchError instance if the request failed, otherwise null.
    • status: The HTTP status code returned by the server.
    • headers: The Headers object from the response.
    • retry: A function that, when called, re-executes the exact same request. This is useful for implementing retry logic.
    const { data, error, status, retry } = await client.endpoint.get()
    
    if (error) {
      console.error(`Request failed with status ${status}`)
      // You can attempt to retry the request
      const retryData = await retry()
    }
  7. Configure request modifiers in EdenTreaty

    main

    When calling a route via the EdenTreaty proxy, you can pass a special object as the first argument to modify the request. This object can contain both your route's expected body and special internal keys (prefixed with $).

    Body Modifiers (Internal Keys):

    • $fetch: Merges directly into the RequestInit object passed to fetch.
    • $headers: Merges into the request headers.
    • $query: Merges into the URL query parameters.
    • $transform: A local transform function applied to the response for this specific call.
    • getRaw: A boolean flag. If true, the function returns the raw Response object instead of the parsed data/error object.

    Execution Options: The proxy call follows the pattern: client.path.method({ body, $query, ... }, { fetch, transform, ... })

    // Example of using modifiers to add query params and headers
    const { data, error } = await client.user.get({
        $query: { id: '123' },
        $headers: {
            Authorization: 'Bearer token'
        }
    })
    
    // Example of getting the raw response
    const response = await client.user.get({ getRaw: true })
  8. Use edenTreaty for a fully type-safe Elysia client

    main

    Eden provides a type-safe client via edenTreaty. To use it, you must export the type of your Elysia application instance from your server file and pass it as a generic type to edenTreaty in your client file. This allows the client to mirror the exact routes, parameters, and body schemas defined on the server.

    // server.ts
    import { Elysia, t } from 'elysia'
    
    const app = new Elysia()
        .get('/', () => 'Hi Elysia')
        .get('/id/:id', ({ params: { id } }) => id)
        .post('/mirror', ({ body }) => body, {
            schema: {
                body: t.Object({
                    id: t.Number(),
                    name: t.String()
                })
            }
        })
        .listen(8080)
    
    export type App = typeof app
    
    // client.ts
    import { edenTreaty } from '@elysia/eden'
    import type { App } from './server'
    
    const app = edenTreaty<App>('http://localhost:8080')
    
    // GET / -> returns 'Hi Elysia'
    const { data: pong } = app.index.get()
    
    // GET /id/1895 -> returns 1895
    const { data: id } = app.id['1895'].get()
    
    // POST /mirror -> returns the body
    const { data: nendoroid } = app.mirror.post({
        id: 1895,
        name: 'Skadi'
    })
  9. Configure the EdenTreaty client via Config

    main

    When initializing an EdenTreaty client, you can provide a Config object to customize how requests are handled. This allows you to set default fetch options, provide a custom fetch implementation, or define global response transformations.

    Key configuration options:

    • $fetch: Default options to pass to the standard fetch API (RequestInit).
    • fetcher: A custom function to replace the default fetch implementation.
    • transform: A Transform function (or array of functions) applied to responses globally.
    const config: EdenTreaty.Config = {
        $fetch: {
            mode: 'cors'
        },
        fetcher: customFetcher,
        transform: (response) => {
            // transform logic
            return response;
        }
    };
  10. Configure Eden Treaty via Treaty.Config

    main

    When initializing treaty, you can provide a config object to customize request behavior. Key configuration options include:

    • headers: Global headers applied to every request. Can be an object, a Headers instance, or a function that returns headers.
    • fetcher: A custom fetch implementation (defaults to the standard fetch).
    • onRequest: A lifecycle hook (or array of hooks) called before a request is sent. It can be used to modify the fetchInit object.
    • onResponse: A lifecycle hook (or array of hooks) called after a response is received. It can be used to transform the response data.
    • throwHttpError: Determines if the client should throw an EdenFetchError on non-2xx status codes. Can be set to true or a custom function (error) => boolean.
    • parseDate: A boolean flag to enable/disable automatic parsing of stringified dates in JSON responses.
    const client = treaty('http://localhost:3000', {
      headers: { Authorization: 'Bearer token' },
      throwHttpError: true,
      parseDate: true,
      onRequest: async (path, init) => {
        // Modify request before it's sent
        return { headers: { 'X-Custom-Header': 'value' } }
      }
    })
  11. Configure edenFetch via EdenFetch.Config

    main

    When initializing edenFetch, you can provide a configuration object to customize behavior globally for all requests made by that client instance.

    Supported configuration options:

    • fetcher: A custom fetch implementation (defaults to globalThis.fetch).
    • throwHttpError: A global setting to determine if HTTP error statuses should throw an EdenFetchError or return an error object.
    const client = edenFetch<App>('http://localhost:3000', {
      fetcher: myCustomFetcher,
      throwHttpError: true
    })