Skip to content

Adapters Overview โ€‹

Asena uses a pluggable adapter system that allows you to choose the HTTP server implementation that best fits your needs. This architectural decision provides flexibility while maintaining a consistent API across all adapters.

What is an Adapter? โ€‹

An adapter is a bridge between Asena's core framework and the underlying HTTP server implementation. It handles:

  • HTTP request/response processing
  • WebSocket connections
  • Middleware execution
  • Static file serving
  • Context wrapping

Available Adapters โ€‹

Asena currently provides two official adapters:

Identical application code runs on both โ€” only the adapter import changes. See Benchmarks for the full comparison across eleven scenarios.

Performance Comparison โ€‹

Published numbers from the benchmark suite โ€” byte-verified workloads, wrk at 400 connections:

AdapterRuntimePlaintextJSON serializationDB read by id
ErgenecoreBun202,066195,49478,023
Hono adapterBun190,030178,85776,780
NestJS ยท ExpressBun131,281119,19529,178
NestJS ยท ExpressNode80,98882,45927,883

TIP

Full methodology โ€” hardware, load generator, isolation and the byte-level conformance gate โ€” is documented on the Benchmarks page.

Feature Comparison โ€‹

FeatureErgenecoreHono
HTTP Methodsโœ…โœ…
WebSocket Supportโœ…โœ…
Middleware Systemโœ…โœ…
Request Validationโœ… (Zod)โœ… (Zod)
Static File Servingโœ…โœ…
Cookie Supportโœ…โœ…
CORS Middlewareโœ…โœ…
Rate Limitingโœ…โœ…

Choosing the Right Adapter โ€‹

Use Ergenecore when: โ€‹

  • โœ… You need maximum performance
  • โœ… You're building a Test or Poc project
  • โœ… You want zero external dependencies
  • โœ… You're using Bun runtime exclusively
  • โœ… You want native Bun optimizations

Use Hono when: โ€‹

  • โœ… You're already familiar with Hono
  • โœ… You're migrating an existing Hono project
  • โœ… You need Hono-specific middleware
  • โœ… You want a battle-tested adapter

Quick Start Comparison โ€‹

Ergenecore Setup โ€‹

typescript
import { AsenaServerFactory } from '@asenajs/asena';
import { createErgenecoreAdapter } from '@asenajs/ergenecore';
import { logger } from './logger';

const adapter = createErgenecoreAdapter();

const server = await AsenaServerFactory.create({
  adapter,
  logger,
  port: 3000
});

await server.start();

Hono Setup โ€‹

typescript
import { AsenaServerFactory } from '@asenajs/asena';
import { createHonoAdapter } from '@asenajs/hono-adapter';
import { AsenaLogger } from '@asenajs/asena-logger';

// createHonoAdapter returns a tuple; createErgenecoreAdapter returns the adapter alone
const [adapter, logger] = createHonoAdapter({ logger: new AsenaLogger() });

const server = await AsenaServerFactory.create({
  adapter,
  logger,
  port: 3000
});

await server.start();

Context API โ€‹

Both adapters implement the same AsenaContext interface, so handler code is identical - only the import path differs.

typescript
import type { Context } from '@asenajs/ergenecore';

// Get parameters - getParam is sync, getQuery/getBody are async
const id = context.getParam('id');
const page = await context.getQuery('page');
const body = await context.getBody<{ name: string }>();

// Send response
return context.send({ id, page, body }, 200);
typescript
import type { Context } from '@asenajs/hono-adapter';

// Get parameters - getParam is sync, getQuery/getBody are async
const id = context.getParam('id');
const page = await context.getQuery('page');
const body = await context.getBody<{ name: string }>();

// Send response
return context.send({ id, page, body }, 200);

The differences are in what context.req gives you: a native Request on Ergenecore, a HonoRequest on Hono. See Context API for the full surface.

Migration Between Adapters โ€‹

TIP

Migrating between adapters means changing the adapter factory and the Context import path. Controllers, services and business logic stay unchanged.

From Hono to Ergenecore โ€‹

typescript
import { Get } from '@asenajs/asena/decorators/http';
// Before: import type { Context } from '@asenajs/hono-adapter';
import type { Context } from '@asenajs/ergenecore';

// The handler body itself does not change
@Get('/:id')
async getUser(context: Context) {
  const id = context.getParam('id');
  return context.send({ id });
}

The bootstrap file changes too, because the factories differ:

typescript
// Before (Hono) - returns a tuple
const [adapter, logger] = createHonoAdapter({ logger: new AsenaLogger() });

// After (Ergenecore) - returns the adapter alone
const adapter = createErgenecoreAdapter();

Advanced Adapter Configuration โ€‹

Ergenecore Advanced Setup โ€‹

typescript
import { createErgenecoreAdapter } from '@asenajs/ergenecore';

const adapter = createErgenecoreAdapter({
  hostname: '0.0.0.0',
  enableWebSocket: true,
  // Custom WebSocket adapter if needed
  websocketAdapter: customWebSocketAdapter
});

Hono Advanced Setup โ€‹

typescript
import { createHonoAdapter } from '@asenajs/hono-adapter';
import { logger } from './logger';

// Single argument: either a bare logger, or an options object containing one
const [adapter, asenaLogger] = createHonoAdapter({
  logger,
  strict: false, // match '/health' and '/health/' alike - useful behind a reverse proxy
});

Creating Custom Adapters โ€‹

You can create your own adapter by implementing the AsenaAdapter interface:

typescript
import type { AsenaAdapter } from '@asenajs/asena/adapter';

export class MyCustomAdapter implements AsenaAdapter {
  async start(port: number): Promise<void> {
    // Implementation
  }

  registerRoute(method: string, path: string, handler: Function): void {
    // Implementation
  }

  // ... implement other required methods
}

TIP

Check the Ergenecore source code for a complete implementation example. or Check the Hono-adapter source code for a complete implementation example.

stop() has to reach your WebSocket layer

server.stop() calls the adapter's stop() before it runs any @OnStop hook, and an adapter with WebSocket support is responsible for tearing that layer down from there โ€” clearing heartbeat timers and calling the WebSocket transport's optional destroy().

Both official adapters do this now. Neither did before: destroy() had no call site anywhere in the framework, so a Redis-backed multi-pod setup leaked a subscriber and a publisher connection on every stop.

Recommendations โ€‹

For New Projects โ€‹

Start with Ergenecore for optimal performance and native Bun features.

bash
bun add @asenajs/ergenecore zod

For Existing Hono Projects โ€‹

Use the Hono adapter for seamless migration and reuse of existing middleware.

bash
bun add @asenajs/hono-adapter hono zod

For Maximum Performance โ€‹

Ergenecore provides:

  • Native Bun optimizations
  • Minimal dependency overhead

Released under the MIT License.