Custom Adapters
Deploy EreoJS to different runtimes and platforms.
Overview
EreoJS is built on standard Web APIs and can run on any platform that supports them. Adapters provide the glue between EreoJS and specific runtimes.
Built-in Adapters
Bun Adapter (Default)
ts
import { defineConfig } from '@ereo/core'
export default defineConfig({
build: {
target: 'bun' // Default
}
})Cloudflare Workers
ts
export default defineConfig({
build: {
target: 'cloudflare'
}
})Node.js
ts
export default defineConfig({
build: {
target: 'node'
}
})Deno
ts
export default defineConfig({
build: {
target: 'deno'
}
})Creating a Custom Adapter
Adapter Interface
ts
interface Adapter {
name: string
build?: (options: BuildOptions) => Promise<void>
serve: (app: EreoApp, options: ServeOptions) => Server | Promise<Server>
}
interface BuildOptions {
outDir: string
minify: boolean
sourcemap: boolean
}
interface ServeOptions {
port: number
hostname: string
}Basic Adapter Example
ts
// adapters/custom.ts
import type { Adapter, EreoApp } from '@ereo/core'
export function customAdapter(): Adapter {
return {
name: 'custom-adapter',
async build({ outDir, minify }) {
// Custom build logic
console.log(`Building to ${outDir}`)
},
serve(app, { port, hostname }) {
// Create server using the app's fetch handler
const server = createServer(async (req, res) => {
const request = toWebRequest(req)
const response = await app.handle(request)
writeResponse(res, response)
})
server.listen(port, hostname)
return server
}
}
}Using the Adapter
ts
// ereo.config.ts
import { defineConfig } from '@ereo/core'
import { customAdapter } from './adapters/custom'
export default defineConfig({
adapter: customAdapter()
})Platform-Specific Adapters
AWS Lambda
ts
// adapters/lambda.ts
import type { Adapter, EreoApp } from '@ereo/core'
import type { APIGatewayProxyEvent, Context } from 'aws-lambda'
export function lambdaAdapter(): Adapter {
return {
name: 'lambda',
async build({ outDir }) {
// Bundle for Lambda
await Bun.build({
entrypoints: ['./src/lambda-handler.ts'],
outdir: outDir,
target: 'node',
minify: true
})
},
serve(app) {
// Return Lambda handler instead of server
return async (event: APIGatewayProxyEvent, context: Context) => {
const request = lambdaEventToRequest(event)
const response = await app.handle(request)
return responseToLambda(response)
}
}
}
}
function lambdaEventToRequest(event: APIGatewayProxyEvent): Request {
const url = `https://${event.headers.host}${event.path}`
const searchParams = new URLSearchParams(event.queryStringParameters || {})
return new Request(`${url}?${searchParams}`, {
method: event.httpMethod,
headers: new Headers(event.headers as Record<string, string>),
body: event.body
})
}
function responseToLambda(response: Response) {
return {
statusCode: response.status,
headers: Object.fromEntries(response.headers),
body: response.body,
isBase64Encoded: false
}
}Fastify
ts
// adapters/fastify.ts
import Fastify from 'fastify'
import type { Adapter, EreoApp } from '@ereo/core'
export function fastifyAdapter(): Adapter {
return {
name: 'fastify',
serve(app, { port, hostname }) {
const fastify = Fastify()
fastify.all('*', async (request, reply) => {
const webRequest = new Request(
`http://${request.hostname}${request.url}`,
{
method: request.method,
headers: request.headers as Record<string, string>,
body: request.body ? JSON.stringify(request.body) : undefined
}
)
const response = await app.handle(webRequest)
reply
.status(response.status)
.headers(Object.fromEntries(response.headers))
.send(await response.text())
})
fastify.listen({ port, host: hostname })
return fastify.server
}
}
}Express
ts
// adapters/express.ts
import express from 'express'
import type { Adapter, EreoApp } from '@ereo/core'
export function expressAdapter(): Adapter {
return {
name: 'express',
serve(app, { port, hostname }) {
const server = express()
server.use(async (req, res) => {
const protocol = req.protocol
const host = req.get('host')
const url = `${protocol}://${host}${req.originalUrl}`
const webRequest = new Request(url, {
method: req.method,
headers: req.headers as Record<string, string>,
body: ['GET', 'HEAD'].includes(req.method) ? undefined : req.body
})
const response = await app.handle(webRequest)
res.status(response.status)
response.headers.forEach((value, key) => res.set(key, value))
res.send(await response.text())
})
return server.listen(port, hostname)
}
}
}Adapter Hooks
Adapters can hook into the build and serve lifecycle:
ts
export function myAdapter(): Adapter {
return {
name: 'my-adapter',
// Called before build starts
async beforeBuild(options) {
console.log('Preparing build...')
},
// Main build logic
async build(options) {
// Build application
},
// Called after build completes
async afterBuild(options, result) {
console.log(`Built in ${result.duration}ms`)
},
// Called before server starts
async beforeServe(app, options) {
console.log('Starting server...')
},
// Main serve logic
serve(app, options) {
return server
},
// Called after server starts
async afterServe(server) {
console.log('Server ready')
}
}
}Static Asset Handling
Handle static assets in your adapter:
ts
serve(app, { port, hostname }) {
const server = Bun.serve({
port,
hostname,
async fetch(request) {
const url = new URL(request.url)
// Serve static files
if (url.pathname.startsWith('/static/')) {
const file = Bun.file(`./dist${url.pathname}`)
if (await file.exists()) {
return new Response(file)
}
}
// Handle with EreoJS
return app.handle(request)
}
})
return server
}Environment Detection
Detect runtime environment:
ts
function detectRuntime() {
if (typeof Bun !== 'undefined') return 'bun'
if (typeof Deno !== 'undefined') return 'deno'
if (typeof process !== 'undefined' && process.versions?.node) return 'node'
if (typeof globalThis.caches !== 'undefined') return 'cloudflare'
return 'unknown'
}