Skip to content

Instantly share code, notes, and snippets.

@bogeychan
Last active April 3, 2025 21:14
Show Gist options
  • Save bogeychan/39290d2dcf468389c7b82dd8bdc7b2e0 to your computer and use it in GitHub Desktop.
Save bogeychan/39290d2dcf468389c7b82dd8bdc7b2e0 to your computer and use it in GitHub Desktop.
Basic Bun HTTP2 Adapter for ElysiaJS
// based on https://github.com/elysiajs/elysia/blob/main/src/adapter/bun/index.ts
import type { ElysiaAdapter, MaybePromise } from "elysia";
import { BunAdapter } from "elysia/adapter/bun";
import { isNumericString } from "elysia/utils";
import { ReadStream as NodeReadStream } from "node:fs";
import {
createSecureServer,
type Http2ServerRequest,
type Http2ServerResponse,
} from "node:http2";
async function onRequest(
this: (request: Request) => MaybePromise<Response>,
req: Http2ServerRequest,
res: Http2ServerResponse
) {
const { method } = req;
const headers: Record<string, string> = {};
for (const name in req.headers) {
if (name.startsWith(":")) {
continue;
}
headers[name] = req.headers[name]!.toString();
}
const request = new Request(
`https://${req.authority ?? "localhost"}${req.url}`,
{
headers,
method,
body: NodeReadStream.toWeb(req) as unknown as ReadableStream<any>,
}
);
let response = this(request);
if (response instanceof Promise) {
response = await response;
}
res.statusCode = response.status;
response.headers.forEach((value, key) => res.setHeader(key, value));
if (response.body) {
NodeReadStream.fromWeb(
response.body as unknown as import("stream/web").ReadableStream<any>
).pipe(res);
} else {
res.end();
}
}
export const HTTP2Adapter: ElysiaAdapter = {
...BunAdapter,
name: "bun-http2",
listen(app) {
return (options, callback) => {
app.compile();
if (typeof options === "string") {
if (!isNumericString(options))
throw new Error("Port must be a numeric value");
options = parseInt(options);
}
const fetch = app.fetch;
// @ts-ignore
const { cert, key } = app.config.serve;
const server = createSecureServer({ cert, key }, onRequest.bind(fetch));
// @ts-expect-error
app.server = {
stop() {
server?.close();
},
};
function listenerCallback() {
if (app.event.start)
for (let i = 0; i < app.event.start.length; i++)
app.event.start[i].fn(app);
if (callback) callback(app.server!);
}
if (typeof options === "object") {
// @ts-ignore
const { port, hostname } = options;
server.listen(port, hostname, listenerCallback);
} else {
server.listen(options, listenerCallback);
}
process.on("beforeExit", () => {
if (app.server) {
app.server.stop();
app.server = null;
if (app.event.stop)
for (let i = 0; i < app.event.stop.length; i++)
app.event.stop[i].fn(app);
}
});
// @ts-expect-error private
app.promisedModules.then(() => Bun.gc(false));
};
},
};
import { Elysia, t } from "elysia";
import { readFileSync } from "fs";
import { HTTP2Adapter } from "./adapter";
export const app = new Elysia({
adapter: HTTP2Adapter,
serve: {
cert: readFileSync("./cert.pem", { encoding: "utf8" }),
key: readFileSync("./key.pem", { encoding: "utf8" }),
},
})
.get("/", "yay")
.get("/json", () => ({ my: "json" }))
.post("/json", ({ body: { name } }) => `whelp ${name}!`, {
body: t.Object({
name: t.String(),
}),
})
.get("/noop", () => {})
.listen(3000);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment