Files
asepharyana-hub-guide/skills/elysiajs/SKILL.md
T
asepharyana e513cddd68 feat(hub-guide): expand plugin with 26 best-practice skills, hooks, and references
Transform hub-guide from a single-skill Hub monorepo guide into a
comprehensive programming best-practice plugin covering all situations.

Skills (26):
- Core: engineering-principles, clean-code, clean-architecture,
  design-patterns, testing, error-handling, security, api-design,
  git-workflow, documentation, logging-observability, performance
- Languages: typescript, python, rust, go
- Frameworks: react-frontend, elysiajs, hono-backend, drizzle-database, nextjs
- Infrastructure: docker, ci-cd, monitoring
- Monorepo: monorepo, hub-guide (existing)

Hooks:
- SessionStart: auto-detect project type and activate relevant skills
- PreToolUse (Write|Edit): inject language-specific rules per file type

Reference files for deep dives:
- clean-architecture/references/solid.md (SOLID + component principles)
- design-patterns/references/catalog.md (full GoF catalog with examples)
- testing/references/mocks.md (test double taxonomy)

Restructure plugin to modern skills/ directory format.
2026-07-25 11:35:16 +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, or whenever the user mentions "Elysia," "ElysiaJS," "Eden," "Eden Treaty," "Bun," "Elysia plugin," or "Elysia validation."

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