Skip to content

Adapters and Framework Utilities

Zelavis has a clean two-layer integration model:

  1. Adapters = the environment Zelavis runs on. Node, Bun, Cloudflare Workers, Vercel, Netlify. Adapters provide infrastructure: database driver, KV store, file storage.
  2. Framework utilities = small helper functions that wrap zelavis.fetch(request) for a specific host framework (Express, Hono, Fastify, etc.). They are not adapters — they are convenience functions.

For fetch-native hosts (Cloudflare Workers, Bun, Next.js App Router, etc.) you do not need a framework utility at all — call zelavis.fetch(request) directly.

import { Zelavis } from "zelavis";
import { nodeAdapter } from "zelavis/adapters/node";
import { createNodeServer } from "zelavis/node";
const zelavis = new Zelavis({ adapter: nodeAdapter() });
const server = await createNodeServer(zelavis);
server.listen(3000);
import express from "express";
import { Zelavis } from "zelavis";
import { nodeAdapter } from "zelavis/adapters/node";
import { expressMiddleware } from "zelavis/express";
const zelavis = new Zelavis({ adapter: nodeAdapter() });
const app = express();
app.use(expressMiddleware(zelavis));
app.listen(3000);
import { Hono } from "hono";
import { Zelavis } from "zelavis";
import { cloudflareAdapter } from "zelavis/adapters/cloudflare";
import { honoMiddleware } from "zelavis/hono";
export default {
fetch(request: Request, env: CloudflareAdapterEnv) {
const zelavis = new Zelavis({ adapter: cloudflareAdapter({ env }) });
const app = new Hono();
app.use(honoMiddleware(zelavis));
return app.fetch(request);
},
};

Next.js App Router (fetch-native, no helper needed)

Section titled “Next.js App Router (fetch-native, no helper needed)”
import { Zelavis } from "zelavis";
import { vercelAdapter } from "zelavis/adapters/vercel";
const zelavis = new Zelavis({ adapter: vercelAdapter() });
export async function GET(request: Request) {
return zelavis.fetch(request);
}
import { Zelavis } from "zelavis";
import { cloudflareAdapter } from "zelavis/adapters/cloudflare";
export default {
fetch(request: Request, env) {
const zelavis = new Zelavis({ adapter: cloudflareAdapter({ env }) });
return zelavis.fetch(request);
},
};

The adapter is the environment Zelavis runs on. It provides:

  • A database driver default
  • KV / file storage defaults
  • Dashboard settings persistence

Available adapters under zelavis/adapters:

AdapterImport pathProvides
Node.jszelavis/adapters/nodebetter-sqlite3, file dashboard settings, local KV/files
Bunzelavis/adapters/bunbun:sqlite, file dashboard settings, local KV/files
Cloudflare Workerszelavis/adapters/cloudflareD1, KV, R2 from worker bindings
Vercelzelavis/adapters/vercelInjected Vercel Blob and KV resources
Netlifyzelavis/adapters/netlifyNetlify Blobs for KV and files

All adapters are also re-exported from the barrel zelavis/adapters under both their canonical names and zelavisX aliases:

import {
zelavisNode,
zelavisBun,
zelavisCloudflare,
zelavisVercel,
zelavisNetlify,
} from "zelavis/adapters";

Framework utilities are simple functions that take a Zelavis instance and return whatever shape the host framework expects:

FrameworkImportReturns
Expresszelavis/expressexpressMiddleware(zelavis)RequestHandler
Honozelavis/honohonoMiddleware(zelavis)MiddlewareHandler
Fastifyzelavis/fastifyfastifyPlugin(zelavis)FastifyPluginAsync
h3zelavis/h3h3Handler(zelavis)h3 handler
Elysiazelavis/elysiaelysiaPlugin(zelavis)Elysia plugin instance
Next.js Pages Routerzelavis/nextjs/pagesnextjsPagesRouterHandler(zelavis, options?)NextApiHandler
Node HTTP serverzelavis/nodecreateNodeServer(zelavis)Promise<http.Server>

Each utility internally lazy-initializes the runtime on first request, so you can construct your Zelavis instance at module top level.

For new environments, build an adapter with defineAdapter:

import { defineAdapter } from "zelavis";
export function herokuAdapter() {
return defineAdapter({
name: "heroku",
async resolve(_options) {
// Reuse nodeAdapter() resolve logic, then override Heroku specifics
return {
// coreServices, resources, metadata
};
},
});
}

The adapter shape is just:

interface ZelavisAdapter {
name: string;
resolve?(options: ZelavisOptions):
| ZelavisResolvedPlatformOptions
| Promise<ZelavisResolvedPlatformOptions>;
}

If a host supports runtime plugin installs, its adapter should expose that through resources.plugins. The plugin activation controller declares whether the host can apply runtime installs, resolve uploaded ESM specifiers, and isolate plugin execution. This keeps Node-style filesystem caches, Cloudflare-style worker dispatch, and other host mechanics outside portable core.

In earlier iterations the framework integration was modeled as an “adapter” object that the runtime mounted (zelavis.adapter.expressMiddleware()). We removed that because framework integration is not actually an adapter pattern — it’s a small request/response converter. The new shape (expressMiddleware(zelavis)) is simpler, has no lifecycle coupling, and produces clean type inference.

The only real adapter pattern in Zelavis is the environment adapter, and that’s what defineAdapter is for.