Files
asepharyana-hub-guide/skills/elysiajs/SKILL.md
T
asepharyana 5780d94ccf feat(hub-guide): language-agnostic auto-trigger across all skills
- Add principle 27: 'Language-Agnostic Auto-Trigger' to engineering-principles
- Remove hardcoded English-only keyword lists from all 26 skill descriptions
- Replace with concept-based detection: triggers from code context, project files,
  and file types regardless of spoken language
- Update detect-project.sh hook output with language-agnostic message
2026-07-25 12:59:43 +07:00

5.1 KiB

name, description
name description
elysiajs ElysiaJS (Bun) best practices — Eden Treaty, plugins, type-safe routes, Elysia validation, and middleware. Use when building ElysiaJS backend APIs. Triggers when working with this framework's files and patterns, not just explicit mentions."

ElysiaJS Best Practices

Project Structure

src/
├── modules/           # Feature modules
│   ├── users/
│   │   ├── routes.ts       # Elysia routes (thin)
│   │   ├── service.ts      # Business logic
│   │   └── repository.ts   # Data access
│   └── orders/
├── plugins/           # Custom Elysia plugins
├── lib/               # Shared utilities
├── db/                # Database schema, migrations
└── index.ts           # App entry point

Route Definition (Type-Safe)

import { Elysia, t } from 'elysia';
import { userService } from './service';

const users = new Elysia({ prefix: '/users' })
  .model({
    'user.create': t.Object({
      email: t.String({ format: 'email' }),
      name: t.Optional(t.String({ minLength: 1 })),
    }),
    'user.response': t.Object({
      id: t.String(),
      email: t.String(),
      name: t.Optional(t.String()),
    }),
  })
  .get('/', async ({ query }) => {
    const result = await userService.list(query);
    return result;
  }, {
    query: t.Object({
      page: t.Optional(t.Numeric({ minimum: 1 })),
      limit: t.Optional(t.Numeric({ minimum: 1, maximum: 100 })),
    }),
    response: t.Array(t.Ref('user.response')),
  })
  .post('/', async ({ body }) => {
    const user = await userService.create(body);
    return user;
  }, {
    body: t.Ref('user.create'),
    response: t.Ref('user.response'),
    detail: { summary: 'Create user', tags: ['Users'] },
  });

export { users };

Eden Treaty (Full-Stack Type Safety)

// Server (route definition inline above creates Eden types automatically)

// Client — automatically typed
import { treaty } from '@elysiajs/eden';
import type { App } from '../server';

const api = treaty<App>('http://localhost:3000');

// Fully typed — autocomplete for paths, params, response
const { data, error } = await api.users.index.get({ query: { page: 1, limit: 20 } });
// data is typed as UserResponse[]

Plugins Pattern

// Custom plugin — encapsulate cross-cutting concerns
import { Elysia } from 'elysia';

const authPlugin = (app: Elysia) =>
  app
    .decorate('auth', new AuthService())
    .derive(({ headers, auth }) => {
      const token = headers.authorization?.split(' ')[1];
      const user = token ? auth.verify(token) : null;
      return { user };
    })
    .onError(({ code, error }) => {
      if (code === 'VALIDATION') return { error: error.message };
    });

// Apply to app
const app = new Elysia()
  .use(authPlugin)
  .use(cors())
  .use(swagger())
  .group('/api/v1', (app) => app.use(users))
  .listen(3000);

Validation

import { t } from 'elysia';

// Reusable models
const PaginationModel = t.Object({
  page: t.Numeric({ minimum: 1, default: 1 }),
  limit: t.Numeric({ minimum: 1, maximum: 100, default: 20 }),
});

const ErrorModel = t.Object({
  error: t.String(),
  details: t.Optional(t.Array(t.Object({
    field: t.String(),
    message: t.String(),
  }))),
});

// Use `model()` to share across routes
const app = new Elysia()
  .model({
    pagination: PaginationModel,
    error: ErrorModel,
  });

Error Handling

import { Elysia, NotFoundError, ValidationError } from 'elysia';

const app = new Elysia()
  .onError(({ code, error, set }) => {
    switch (code) {
      case 'NOT_FOUND':
        set.status = 404;
        return { error: 'Resource not found' };
      case 'VALIDATION':
        set.status = 422;
        return { error: error.message };
      default:
        set.status = 500;
        console.error(error);
        return { error: 'Internal server error' };
    }
  });

Performance

  • Elysia runs on Bun — Bun is fast. No need for extra micro-optimizations initially.
  • Use scoped: true for per-request state isolation.
  • Static routes — use staticPlugin for serving files.
  • WebSocket — built-in WS support, no extra lib needed.

Testing

import { describe, expect, it } from 'bun:test';
import { Elysia } from 'elysia';
import { userRoutes } from './routes';

const app = new Elysia().use(userRoutes);

describe('users', () => {
  it('returns 422 for invalid email', async () => {
    const res = await app
      .handle(new Request('http://localhost/users', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ email: 'not-an-email' }),
      }));
    expect(res.status).toBe(422);
  });
});

Anti-patterns

  • Business logic in route handlers — extract to service layer
  • No validation on inputs — every route must have a schema
  • Mixing Elysia/Express patterns — Elysia is not Express
  • any types — Elysia's superpower is type-safety
  • Global state in plugins — use decorator/derive for per-request state
  • Using t.Any() — defeats validation