AbsoluteJS

Data Fetching

Server-side data fetching with complete type safety from database to component.

#Server-Side Fetching

All data fetching happens on the server in your route handlers. Data is passed to components as type-safe props.

No Client Waterfalls

Data is fetched before rendering. No loading states for initial page load.

Secure by Default

Database queries run on the server. Credentials never reach the client.

TS
// Data fetching happens on the server in your route handlers
// The data is passed to components as props: fully typed

.get('/posts', async () => {
  // Fetch data on the server
  const posts = await db.query.posts.findMany({
    with: { author: true }
  });

  // TypeScript knows posts is Post[] with author relation
  return handleReactPageRequest({
    Page: PostList,
    index: asset(manifest, 'PostListIndex'),
    props: { posts }  // Type-safe: PostListProps['posts'] must match
  });
})

#Eden Treaty

For client-side data fetching, AbsoluteJS uses Eden Treaty instead of raw fetch requests. Eden provides end-to-end type safety by deriving types directly from your Elysia server.

Route Validation

TypeScript errors if you try to call a route that doesn't exist on your server.

Request Body Typing

POST/PUT bodies are validated at compile time against your endpoint's schema.

Server Setup

Export the server type from your Elysia app. This is what Eden uses to infer all routes and their types:

TS
// src/backend/server.ts
const app = new Elysia()
  .get('/api/posts', async () => {
    const posts = await db.query.posts.findMany();
    return posts;
  })
  .get('/api/posts/:id', async ({ params }) => {
    return db.query.posts.findFirst({
      where: eq(posts.id, Number(params.id))
    });
  })
  .post('/api/posts', async ({ body }) => {
    return db.insert(posts).values(body).returning();
  }, {
    body: t.Object({
      title: t.String(),
      content: t.String()
    })
  });

// Export the type for Eden Treaty
export type Server = typeof app;

Client Setup

Create a treaty client using your server type:

TS
// src/frontend/eden/treaty.ts
import { treaty } from '@elysiajs/eden';
import type { Server } from '../../backend/server';

const serverUrl =
  typeof window !== 'undefined'
    ? window.location.origin
    : 'http://localhost:3000';

// The Server type comes from your Elysia server export
export const server = treaty<Server>(serverUrl);

Usage

Call your API endpoints with full type safety. Routes, params, and request bodies are all validated at compile time:

TS
// Client-side usage: fully type-safe!
import { server } from '../eden/treaty';

// TypeScript knows this route exists and returns Post[]
const { data: posts, error } = await server.api.posts.get();

// error is typed based on your server's status responses
if (error) {
  // error.value contains the error message from the server
  console.error(error.value);
  return;
}

// Route params are type-checked
const { data: post } = await server.api.posts({ id: '123' }).get();

// Request body is validated at compile time
// TypeScript error if title or content is missing!
const { data: newPost } = await server.api.posts.post({
  title: 'Hello World',
  content: 'My first post'
});

// This would be a TypeScript error: route doesn't exist:
// server.api.nonexistent.get()  ❌

Compile-Time Safety

Eden Treaty catches API mistakes before your code runs:

TS
// Eden Treaty prevents common API mistakes at compile time

// ❌ TypeScript Error: Property 'users' does not exist
server.api.users.get();

// ❌ TypeScript Error: Property 'name' is missing
server.api.posts.post({ title: 'Hello' });

// ❌ TypeScript Error: Argument of type 'number' is not assignable
server.api.posts({ id: 123 }).get();  // id must be string

// ✅ All correct: TypeScript is happy
const { data, error } = await server.api.posts.get();
if (error) {
  console.error(error);
  return;
}
// data is typed as Post[]
console.log(data);

#End-to-End Type Flow

Types flow seamlessly from your database schema through your route handlers to your components. TypeScript catches mismatches at compile time.

End-to-End Type Safety

SERVER

1

Schema

Define your tables

pgTable('users', { id, name, email })
2

Drizzle ORM

Infer TypeScript types

type User = typeof users.$inferSelect
3

Route Handler

Query and return data

db.select().from(users)

CLIENT

4

Eden Treaty

Type-safe API calls

const { data, error } = api.users.get()
5

Component

Fully typed props

type Props = { user: User }

TypeScript validates at every step: errors caught at compile time, not runtime

TS
// Types flow from database schema to components

// 1. Database schema (Drizzle)
const posts = pgTable('posts', {
  id: serial('id').primaryKey(),
  title: varchar('title', { length: 255 }).notNull(),
  content: text('content').notNull(),
  authorId: integer('author_id').references(() => users.id)
});

// 2. Inferred types
type Post = typeof posts.$inferSelect;
// { id: number; title: string; content: string; authorId: number | null }

// 3. Props type
type PostPageProps = {
  post: Post;
  author: User;
};

// 4. Component receives correctly typed data
export const PostPage = ({ post, author }: PostPageProps) => (
  <article>
    <h1>{post.title}</h1>
    <p>By {author.name}</p>
    <div>{post.content}</div>
  </article>
);
1
Schema → Types
Drizzle/Prisma infer types from your schema
2
Query → Results
Query results are typed based on your schema
3
Props → Components
handleReactPageRequest validates prop types

#Type-Safe Status Responses

Status responses are also type-safe. Elysia's status() function returns typed responses for any HTTP status code:

TS
// Status responses are also type-safe with Elysia

.get('/posts/:id', async ({ params, status }) => {
  const post = await db.query.posts.findFirst({
    where: eq(posts.id, Number(params.id))
  });

  // Return status with code and message string
  if (!post) {
    return status(404, 'Post not found');
  }

  return handleReactPageRequest({
    Page: PostPage,
    index: indexPath,
    props: { post }
  });
}, {
  params: t.Object({ id: t.String() })
})