Skip to main content
Elysia uses an adapter system to support multiple JavaScript runtimes and deployment targets. Adapters abstract runtime-specific APIs while maintaining consistent behavior across environments.

Available adapters

Elysia ships with three built-in adapters:
  • BunAdapter - Optimized for Bun runtime (default)
  • CloudflareAdapter - For Cloudflare Workers
  • WebStandardAdapter - Web Standard compatible (Node.js, Deno, etc.)

Bun adapter

The default adapter for Bun runtime, providing native performance optimizations:
import { Elysia } from 'elysia'
import { BunAdapter } from 'elysia/adapter/bun'

const app = new Elysia({
  adapter: BunAdapter // Default, can be omitted
})
  .get('/', () => 'Hello from Bun')
  .listen(3000)

Bun-specific features

The Bun adapter leverages Bun’s native capabilities:
import { Elysia } from 'elysia'

const app = new Elysia()
  .get('/file', () => Bun.file('package.json'))
  .listen({
    port: 3000,
    // Bun.serve options
    development: true,
    reusePort: true,
    idleTimeout: 30
  })
The Bun adapter uses Bun.serve() under the hood, providing access to all Bun server options.

Native static routes

Bun adapter supports native static route optimization:
import { Elysia } from 'elysia'

const app = new Elysia({
  nativeStaticResponse: true, // Default
  aot: true // Enable static route compilation
})
  .get('/static', 'Static response')
  .listen(3000)
Static routes are pre-compiled and served directly by Bun’s router:
// From adapter/bun/index.ts
const routes = {
  '/static': new Response('Static response'),
  '/api': {
    GET: handlerFunction,
    POST: handlerFunction
  }
}

Bun.serve({
  routes,
  fetch: app.fetch
})

Cloudflare Workers adapter

Optimized for Cloudflare Workers environment:
import { Elysia } from 'elysia'
import { CloudflareAdapter } from 'elysia/adapter/cloudflare-worker'

const app = new Elysia({
  adapter: CloudflareAdapter
})
  .get('/', () => 'Hello from Cloudflare')
  .compile()

export default app
Cloudflare Workers don’t support the .listen() method. Export the Elysia instance directly instead.

Cloudflare-specific configuration

The Cloudflare adapter handles error responses differently:
// From adapter/cloudflare-worker/index.ts
export const CloudflareAdapter: ElysiaAdapter = {
  ...WebStandardAdapter,
  name: 'cloudflare-worker',
  composeGeneralHandler: {
    ...WebStandardAdapter.composeGeneralHandler,
    error404(hasEventHook, hasErrorHook, afterHandle) {
      const { code } = WebStandardAdapter.composeGeneralHandler.error404(
        hasEventHook,
        hasErrorHook,
        afterHandle
      )

      return {
        code,
        declare: hasErrorHook
          ? ''
          : `const error404Message=notFound.message.toString()\n` +
            `const error404={clone:()=>new Response(error404Message,{status:404})}\n`
      }
    }
  },
  beforeCompile(app) {
    app.handleError = composeErrorHandler(app)
    for (const route of app.routes) route.compile()
  },
  listen() {
    return () => {
      console.warn(
        'Cloudflare Worker does not support listen method. Please export default Elysia instance instead.'
      )
    }
  }
}

Environment bindings

Access Cloudflare bindings through context:
import { Elysia } from 'elysia'
import { CloudflareAdapter } from 'elysia/adapter/cloudflare-worker'

const app = new Elysia({ adapter: CloudflareAdapter })
  .get('/kv', async ({ env }) => {
    // Access KV, R2, Durable Objects, etc.
    return await env.MY_KV.get('key')
  })
  .compile()

export default app

Web Standard adapter

Provides compatibility with Web Standard Request/Response APIs:
import { Elysia } from 'elysia'
import { WebStandardAdapter } from 'elysia/adapter/web-standard'

const app = new Elysia({
  adapter: WebStandardAdapter
})
  .get('/', () => 'Hello from Web Standard')

export default app

Node.js usage

Use with Node.js HTTP servers:
import { Elysia } from 'elysia'
import { WebStandardAdapter } from 'elysia/adapter/web-standard'
import { createServer } from 'node:http'

const app = new Elysia({
  adapter: WebStandardAdapter
})
  .get('/', () => 'Hello Node.js')

createServer(app.fetch).listen(3000)

Deno usage

import { Elysia } from 'elysia'
import { WebStandardAdapter } from 'elysia/adapter/web-standard'

const app = new Elysia({
  adapter: WebStandardAdapter
})
  .get('/', () => 'Hello Deno')

Deno.serve(app.fetch)

Adapter architecture

Adapters implement the ElysiaAdapter interface:
// From adapter/types.ts
export interface ElysiaAdapter {
  name: string
  
  // Server lifecycle
  listen(app: AnyElysia): (
    options: string | number | Partial<Serve>,
    callback?: ListenCallback
  ) => void
  
  stop?(app: AnyElysia, closeActiveConnections?: boolean): Promise<void>
  
  // Response handlers
  handler: {
    mapResponse(response: unknown, set: Context['set'], ...params: unknown[]): unknown
    mapEarlyResponse(response: unknown, set: Context['set'], ...params: unknown[]): unknown
    mapCompactResponse(response: unknown, ...params: unknown[]): unknown
    createStaticHandler?(...params: unknown[]): (() => unknown) | undefined
    createNativeStaticHandler?(...params: unknown[]): (() => MaybePromise<Response>) | undefined
  }
  
  // Code generation
  composeHandler: {
    headers: string
    parser: Record<'json' | 'text' | 'urlencoded' | 'arrayBuffer' | 'formData', (isOptional: boolean) => string>
  }
  
  // General handler composition
  composeGeneralHandler: {
    createContext(app: AnyElysia): string
    error404(hasEventHook: boolean, hasErrorHook: boolean, afterResponseHandler?: string): {
      declare: string
      code: string
    }
  }
  
  // WebSocket support
  ws?(app: AnyElysia, path: string, handler: AnyWSLocalHook): unknown
  
  // Compilation hooks
  beforeCompile?(app: AnyElysia): void
}

Custom adapters

Create custom adapters for specialized runtimes:
import { WebStandardAdapter } from 'elysia/adapter/web-standard'
import type { ElysiaAdapter } from 'elysia/adapter'

export const CustomAdapter: ElysiaAdapter = {
  ...WebStandardAdapter,
  name: 'custom-runtime',
  
  listen(app) {
    return (options, callback) => {
      app.compile()
      
      // Custom server implementation
      const server = customRuntime.createServer({
        port: typeof options === 'number' ? options : options.port,
        fetch: app.fetch
      })
      
      if (callback) callback(server)
    }
  },
  
  handler: {
    ...WebStandardAdapter.handler,
    mapResponse(response, set) {
      // Custom response mapping
      return new Response(
        JSON.stringify(response),
        {
          status: set.status,
          headers: set.headers
        }
      )
    }
  }
}

Handler composition

Adapters control how request handlers are composed:
// From adapter/web-standard/index.ts
composeHandler: {
  headers:
    'c.headers={}\n' +
    'for(const [k,v] of c.request.headers.entries())' +
    'c.headers[k]=v\n',
  
  parser: {
    json(isOptional) {
      if (isOptional)
        return `try{c.body=await c.request.json()}catch{}\n`
      return `c.body=await c.request.json()\n`
    },
    text() {
      return `c.body=await c.request.text()\n`
    },
    urlencoded() {
      return `c.body=parseQuery(await c.request.text())\n`
    },
    formData(isOptional) {
      // Complex form data parsing logic
      return formDataParser
    }
  }
}

Response mapping

Adapters transform handler returns into HTTP responses:
// Simplified from adapter/web-standard/handler.ts
export const mapResponse = (
  response: unknown,
  set: Context['set']
): Response => {
  if (response instanceof Response)
    return response
  
  if (typeof response === 'string')
    return new Response(response, {
      headers: set.headers
    })
  
  return new Response(
    JSON.stringify(response),
    {
      status: set.status,
      headers: {
        'content-type': 'application/json',
        ...set.headers
      }
    }
  )
}

System router integration

Some runtimes provide native routing (Bun.serve routes):
const app = new Elysia({
  aot: true,
  systemRouter: true // Use runtime router if available
})
The Bun adapter generates optimized route maps:
// From adapter/bun/index.ts
const routes = {
  '/api/users': {
    GET: getUsersHandler,
    POST: createUserHandler
  },
  '/api/users/123': getUserByIdHandler,
  '/static': new Response('Static content')
}

Bun.serve({
  routes,
  fetch: app.fetch
})

Best practices

Choose the right adapter

// Production on Bun
const app = new Elysia() // BunAdapter is default

// Cloudflare Workers
const app = new Elysia({
  adapter: CloudflareAdapter
}).compile()

// Node.js or Deno
const app = new Elysia({
  adapter: WebStandardAdapter
})

Adapter-specific optimizations

import { Elysia } from 'elysia'

// Enable Bun optimizations
const app = new Elysia({
  aot: true,
  nativeStaticResponse: true,
  systemRouter: true
})

Export correctly for each platform

// Bun - use .listen()
app.listen(3000)

// Cloudflare Workers - export instance
export default app.compile()

// Web Standard - export fetch
export default { fetch: app.fetch }

Build docs developers (and LLMs) love