Elysia Integration

Register routes and handle errors with Elysia framework.

Installation

bun add elysia @elysiajs/cors @elysiajs/jwt

Import the Elysia integration:

import {
  registerElysiaRoutes,
  onionErrorHandler,
  type ElysiaContext,
  type ElysiaMiddleware,
} from '@cosmneo/onion-lasagna/http/frameworks/elysia';

Quick Start

import { Elysia } from 'elysia';
import { registerElysiaRoutes, onionErrorHandler } from '@cosmneo/onion-lasagna/http/frameworks/elysia';
import { bootstrapProjectManagement } from './bootstrap';

const { routes } = bootstrapProjectManagement();

const app = new Elysia()
  .onError(({ error }) => onionErrorHandler({ error }))
  .get('/health', () => ({ status: 'ok' }));

registerElysiaRoutes(app, routes);

app.listen(3000);

Registering Routes

The registerElysiaRoutes function registers routes with your Elysia app:

import { serverRoutes } from '@cosmneo/onion-lasagna/http/server';
import { registerElysiaRoutes } from '@cosmneo/onion-lasagna/http/frameworks/elysia';

// Create routes using the builder
const routes = serverRoutes(userRouter)
  .handle('users.get', {
    requestMapper: (req) => ({ userId: req.pathParams.userId }),
    useCase: getUserQuery,
    responseMapper: (output) => ({ status: 200 as const, body: output }),
  })
  .build();

// Register with Elysia
registerElysiaRoutes(app, routes);

Info:

Note: registerElysiaRoutes is a function that mutates the app, not a plugin. Call it with app as the first argument.


Options

interface RegisterElysiaRoutesOptions {
  middlewares?: ElysiaMiddleware[];         // Middlewares applied to all routes
  contextExtractor?: ElysiaContextExtractor; // Extract auth context
}

registerElysiaRoutes(app, routes, {
  middlewares: [authMiddleware],
  contextExtractor: (ctx) => ({
    userId: ctx.store['userId'],
  }),
});

Context Extraction (Protected Routes)

For authenticated routes, provide a contextExtractor to pass auth data to handlers:

import type { ElysiaContext, ElysiaMiddleware } from '@cosmneo/onion-lasagna/http/frameworks/elysia';

// Auth middleware
const authMiddleware: ElysiaMiddleware = async (ctx: ElysiaContext) => {
  const authHeader = ctx.headers['authorization'];
  if (!authHeader?.startsWith('Bearer ')) {
    return new Response(JSON.stringify({ message: 'Unauthorized' }), {
      status: 401,
      headers: { 'Content-Type': 'application/json' },
    });
  }

  const token = authHeader.slice(7);
  const payload = decodeJwt(token);
  ctx.store['userId'] = payload.sub;

  return undefined; // Continue to handler
};

// Register with context extraction
registerElysiaRoutes(app, routes, {
  middlewares: [authMiddleware],
  contextExtractor: (ctx: ElysiaContext) => ({
    userId: ctx.store['userId'] as string,
  }),
});

Error Handler

The onionErrorHandler maps domain errors to HTTP responses:

app.onError(({ error }) => onionErrorHandler({ error }));

Error Mapping

Error TypeHTTP StatusBody
ObjectValidationError400{ errorCode, message, details }
InvalidRequestError400{ errorCode, message, details }
UseCaseError400{ errorCode, message }
AccessDeniedError403{ errorCode, message }
NotFoundError404{ errorCode, message }
ConflictError409{ errorCode, message }
UnprocessableError422{ errorCode, message }
DomainError500Masked
InfraError500Masked
Unknown500Masked

Complete Example

import 'dotenv/config';
import { Elysia } from 'elysia';
import { cors } from '@elysiajs/cors';
import {
  registerElysiaRoutes,
  onionErrorHandler,
  type ElysiaContext,
  type ElysiaMiddleware,
} from '@cosmneo/onion-lasagna/http/frameworks/elysia';
import { bootstrapProjectManagement } from './bootstrap';

const { routes } = bootstrapProjectManagement();

// Auth middleware
const authMiddleware: ElysiaMiddleware = async (ctx: ElysiaContext) => {
  const authHeader = ctx.headers['authorization'];
  if (!authHeader?.startsWith('Bearer ')) {
    return new Response(JSON.stringify({ message: 'Unauthorized' }), {
      status: 401,
      headers: { 'Content-Type': 'application/json' },
    });
  }

  const token = authHeader.slice(7);
  try {
    const [, payloadBase64] = token.split('.');
    const payload = JSON.parse(Buffer.from(payloadBase64!, 'base64').toString());
    ctx.store['userId'] = payload.sub;
  } catch {
    return new Response(JSON.stringify({ message: 'Invalid token' }), {
      status: 401,
      headers: { 'Content-Type': 'application/json' },
    });
  }

  return undefined;
};

// Create app
const app = new Elysia()
  .use(cors())
  .onError(({ error }) => {
    console.error('Error:', error);
    return onionErrorHandler({ error });
  })
  .get('/health', () => ({ status: 'ok' }));

// Register routes with auth
registerElysiaRoutes(app, routes, {
  middlewares: [authMiddleware],
  contextExtractor: (ctx: ElysiaContext) => ({
    userId: ctx.store['userId'] as string,
  }),
});

// Start server
app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});