Advanced H3 features including proxy functionality, Server-Sent Events (SSE), WebSocket support, static file serving, and caching utilities.
Forward requests to other servers with comprehensive proxy options.
/**
* Proxy request to target server
* @param event - H3 event
* @param target - Target URL or server
* @param opts - Proxy configuration options
* @returns Promise resolving to response body
*/
function proxy(
event: H3Event,
target: string,
opts?: ProxyOptions
): Promise<BodyInit | undefined | null>;
/**
* Proxy request with full control (alias for proxy)
* @param event - H3 event
* @param target - Target URL or server
* @param opts - Proxy configuration options
* @returns Promise resolving to response body
*/
function proxyRequest(
event: H3Event,
target: string,
opts?: ProxyOptions
): Promise<BodyInit | undefined | null>;
/**
* Get headers for proxy request
* @param event - H3 event
* @param options - Header processing options
* @returns Headers object for proxy request
*/
function getProxyRequestHeaders(
event: H3Event,
options?: {
ignoredHeaders?: string[];
allowedHeaders?: string[];
}
): Headers;
/**
* Fetch with H3 event context
* @param event - H3 event
* @param req - Request, URL, or string
* @param init - Request initialization options
* @returns Promise resolving to Response
*/
function fetchWithEvent(
event: H3Event,
req: ServerRequest | URL | string,
init?: RequestInit
): Promise<Response>;Usage Examples:
import { proxy, getProxyRequestHeaders, fetchWithEvent } from "h3";
// Basic proxy
const proxyHandler = defineHandler(async (event) => {
return await proxy(event, "https://api.external-service.com");
});
// Proxy with options
const advancedProxyHandler = defineHandler(async (event) => {
return await proxy(event, "https://backend.example.com", {
headers: {
"X-Forwarded-For": getRequestIP(event),
"X-Original-Host": getRequestHost(event)
},
onProxyReq: (proxyReq, req) => {
console.log(`Proxying ${req.method} ${req.url}`);
},
onProxyRes: (proxyRes, req, res) => {
console.log(`Proxy response: ${proxyRes.status}`);
}
});
});
// Custom proxy headers
const customHeadersProxyHandler = defineHandler(async (event) => {
const proxyHeaders = getProxyRequestHeaders(event, {
ignoredHeaders: ["authorization", "cookie"],
allowedHeaders: ["content-type", "accept", "user-agent"]
});
return await proxy(event, "https://public-api.com", {
headers: {
...Object.fromEntries(proxyHeaders.entries()),
"X-API-Key": process.env.EXTERNAL_API_KEY
}
});
});
// Fetch with event context
const fetchHandler = defineHandler(async (event) => {
const response = await fetchWithEvent(
event,
"https://api.example.com/data",
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query: getQuery(event) })
}
);
const data = await response.json();
return { data };
});
// Load balancing proxy
const loadBalancerHandler = defineHandler(async (event) => {
const servers = [
"https://server1.example.com",
"https://server2.example.com",
"https://server3.example.com"
];
const serverIndex = Math.floor(Math.random() * servers.length);
const targetServer = servers[serverIndex];
return await proxy(event, targetServer, {
headers: {
"X-Load-Balancer": "h3-proxy",
"X-Server-Index": serverIndex.toString()
}
});
});Real-time server-to-client communication using Server-Sent Events.
/**
* Create Server-Sent Events stream
* @param event - H3 event
* @param options - SSE configuration options
* @returns Event stream helper object
*/
function createEventStream(
event: H3Event,
options?: EventStreamOptions
): EventStreamHelper;
interface EventStreamHelper {
/**
* Send a data event
* @param data - Data to send (string or object)
*/
push(data: string | any): void;
/**
* Send a named event with data
* @param event - Event name
* @param data - Event data
*/
pushEvent(event: string, data: string | any): void;
/**
* Send an event with full control
* @param message - Complete event message
*/
pushMessage(message: EventStreamMessage): void;
/**
* Close the event stream
*/
close(): void;
/**
* Send keep-alive ping
*/
ping(): void;
}Usage Examples:
import { createEventStream } from "h3";
// Basic SSE stream
const sseHandler = defineHandler((event) => {
const eventStream = createEventStream(event);
// Send initial data
eventStream.push("Connected to stream");
// Send periodic updates
const interval = setInterval(() => {
eventStream.push({
timestamp: Date.now(),
message: "Periodic update"
});
}, 1000);
// Cleanup on client disconnect
event.waitUntil(new Promise((resolve) => {
const checkConnection = setInterval(() => {
if (event.aborted) {
clearInterval(interval);
clearInterval(checkConnection);
eventStream.close();
resolve(undefined);
}
}, 5000);
}));
return eventStream;
});
// Named events SSE
const namedEventsHandler = defineHandler((event) => {
const eventStream = createEventStream(event, {
retry: 3000, // Client retry interval
headers: {
"Cache-Control": "no-cache",
"Connection": "keep-alive"
}
});
// Send different event types
eventStream.pushEvent("user-connected", { userId: "123" });
eventStream.pushEvent("notification", {
type: "info",
message: "Welcome to the stream"
});
// Send with custom message format
eventStream.pushMessage({
event: "custom-event",
data: JSON.stringify({ custom: "data" }),
id: "msg-001",
retry: 5000
});
return eventStream;
});
// Real-time data stream
const realtimeDataHandler = defineHandler(async (event) => {
const eventStream = createEventStream(event);
// Subscribe to real-time data source
const unsubscribe = dataService.subscribe((data) => {
eventStream.push(data);
});
// Handle client disconnect
event.waitUntil(new Promise((resolve) => {
const checkConnection = () => {
if (event.aborted) {
unsubscribe();
eventStream.close();
resolve(undefined);
} else {
setTimeout(checkConnection, 1000);
}
};
checkConnection();
}));
return eventStream;
});
// Chat room SSE
const chatStreamHandler = defineHandler((event) => {
const { roomId } = getRouterParams(event);
const eventStream = createEventStream(event);
// Join chat room
chatService.joinRoom(roomId, (message) => {
eventStream.pushEvent("message", {
id: message.id,
user: message.user,
text: message.text,
timestamp: message.timestamp
});
});
// Send room history
chatService.getHistory(roomId).then(messages => {
eventStream.pushEvent("history", messages);
});
// Handle user leaving
event.waitUntil(new Promise((resolve) => {
const cleanup = () => {
chatService.leaveRoom(roomId);
eventStream.close();
resolve(undefined);
};
if (event.aborted) {
cleanup();
} else {
setTimeout(() => {
if (event.aborted) cleanup();
}, 1000);
}
}));
return eventStream;
});WebSocket handler definitions for real-time bidirectional communication.
/**
* Define WebSocket event hooks
* @param hooks - WebSocket lifecycle hooks
* @returns WebSocket hooks object
*/
function defineWebSocket(hooks: Partial<WSHooks>): Partial<WSHooks>;
/**
* Define WebSocket handler
* @param hooks - WebSocket lifecycle hooks
* @returns Event handler for WebSocket upgrade
*/
function defineWebSocketHandler(hooks: Partial<WSHooks>): EventHandler;
interface WSHooks {
/**
* Called when WebSocket connection is opened
* @param peer - WebSocket peer connection
*/
open(peer: WSPeer): void | Promise<void>;
/**
* Called when message is received
* @param peer - WebSocket peer connection
* @param message - Received message
*/
message(peer: WSPeer, message: WSMessage): void | Promise<void>;
/**
* Called when WebSocket connection is closed
* @param peer - WebSocket peer connection
* @param details - Close details
*/
close(peer: WSPeer, details: WSCloseDetails): void | Promise<void>;
/**
* Called when WebSocket error occurs
* @param peer - WebSocket peer connection
* @param error - Error object
*/
error(peer: WSPeer, error: Error): void | Promise<void>;
/**
* Called during WebSocket upgrade
* @param event - H3 event
*/
upgrade(event: H3Event): void | Promise<void>;
}Usage Examples:
import { defineWebSocketHandler, defineWebSocket } from "h3";
// Basic WebSocket handler
const wsHandler = defineWebSocketHandler({
open(peer) {
console.log("WebSocket connection opened");
peer.send("Welcome to WebSocket server");
},
message(peer, message) {
console.log("Received:", message.text());
// Echo message back
peer.send(`Echo: ${message.text()}`);
},
close(peer, details) {
console.log("WebSocket connection closed:", details.code);
},
error(peer, error) {
console.error("WebSocket error:", error);
}
});
// Chat WebSocket
const chatWsHandler = defineWebSocketHandler({
async open(peer) {
// Store connection in chat room
const roomId = peer.url.searchParams.get("room") || "general";
await chatService.addPeer(roomId, peer);
// Send room info
peer.send(JSON.stringify({
type: "room-info",
roomId,
userCount: await chatService.getUserCount(roomId)
}));
},
async message(peer, message) {
const data = JSON.parse(message.text());
const roomId = peer.url.searchParams.get("room") || "general";
switch (data.type) {
case "chat-message":
// Broadcast to all peers in room
await chatService.broadcast(roomId, {
type: "message",
user: data.user,
text: data.text,
timestamp: Date.now()
});
break;
case "typing":
// Broadcast typing indicator
await chatService.broadcastToOthers(roomId, peer, {
type: "user-typing",
user: data.user
});
break;
}
},
async close(peer) {
const roomId = peer.url.searchParams.get("room") || "general";
await chatService.removePeer(roomId, peer);
}
});
// Game WebSocket
const gameWsHandler = defineWebSocketHandler({
async upgrade(event) {
// Validate game token
const token = getQuery(event).token;
const gameSession = await validateGameToken(token);
if (!gameSession) {
throw HTTPError.status(401, "Invalid game token");
}
// Store game info in context
event.context.gameId = gameSession.gameId;
event.context.playerId = gameSession.playerId;
},
async open(peer) {
const { gameId, playerId } = peer.context;
// Add player to game
await gameService.addPlayer(gameId, playerId, peer);
// Send game state
const gameState = await gameService.getState(gameId);
peer.send(JSON.stringify({
type: "game-state",
state: gameState
}));
},
async message(peer, message) {
const data = JSON.parse(message.text());
const { gameId, playerId } = peer.context;
// Process game action
const result = await gameService.processAction(gameId, playerId, data);
if (result.valid) {
// Broadcast update to all players
await gameService.broadcastUpdate(gameId, result.update);
} else {
// Send error to player
peer.send(JSON.stringify({
type: "error",
message: result.error
}));
}
}
});
// WebSocket with authentication
const authWsHandler = defineWebSocketHandler({
async upgrade(event) {
const token = getHeader(event, "authorization")?.replace("Bearer ", "");
if (!token) {
throw HTTPError.status(401, "Authentication required");
}
const user = await verifyJWTToken(token);
if (!user) {
throw HTTPError.status(401, "Invalid token");
}
event.context.user = user;
},
open(peer) {
const user = peer.context.user;
console.log(`User ${user.id} connected via WebSocket`);
peer.send(JSON.stringify({
type: "authenticated",
user: { id: user.id, name: user.name }
}));
},
async message(peer, message) {
const user = peer.context.user;
const data = JSON.parse(message.text());
// Process authenticated user action
await processUserAction(user, data);
}
});Serve static files with caching, compression, and security features.
/**
* Serve static files from filesystem
* @param event - H3 event
* @param options - Static serving options
* @returns Promise resolving to Response or undefined
*/
function serveStatic(
event: H3Event,
options: ServeStaticOptions
): Promise<Response | undefined>;Usage Examples:
import { serveStatic } from "h3";
// Basic static serving
const staticHandler = defineHandler(async (event) => {
return await serveStatic(event, {
root: "./public",
index: ["index.html"],
dotFiles: "deny"
});
});
// Advanced static serving
const advancedStaticHandler = defineHandler(async (event) => {
return await serveStatic(event, {
root: "./assets",
index: ["index.html", "index.htm"],
dotFiles: "deny",
etag: true,
lastModified: true,
maxAge: 86400, // 24 hours
immutable: event.url.pathname.includes("/static/"),
fallthrough: false,
redirect: true,
extensions: ["html", "htm"]
});
});
// SPA serving with fallback
const spaHandler = defineHandler(async (event) => {
const response = await serveStatic(event, {
root: "./dist",
index: ["index.html"],
fallthrough: true
});
// Fallback to index.html for SPA routes
if (!response && !event.url.pathname.startsWith("/api/")) {
return await serveStatic(event, {
root: "./dist",
index: ["index.html"]
});
}
return response;
});
// Multiple static directories
const multiStaticHandler = defineHandler(async (event) => {
// Try serving from uploads first
if (event.url.pathname.startsWith("/uploads/")) {
const response = await serveStatic(event, {
root: "./uploads",
maxAge: 3600
});
if (response) return response;
}
// Fall back to public directory
return await serveStatic(event, {
root: "./public",
maxAge: 86400
});
});/**
* Proxy configuration options
*/
interface ProxyOptions {
/**
* Additional headers to send
*/
headers?: Record<string, string>;
/**
* Transform request before sending
*/
onProxyReq?: (proxyReq: Request, req: Request) => void;
/**
* Transform response before returning
*/
onProxyRes?: (proxyRes: Response, req: Request, res: Response) => void;
/**
* Handle proxy errors
*/
onError?: (error: Error, req: Request, res: Response) => void;
/**
* Custom agent for requests
*/
agent?: any;
/**
* Timeout in milliseconds
*/
timeout?: number;
/**
* Follow redirects
*/
followRedirects?: boolean;
}/**
* Server-Sent Events options
*/
interface EventStreamOptions {
/**
* Client retry interval in milliseconds
*/
retry?: number;
/**
* Additional response headers
*/
headers?: Record<string, string>;
/**
* Auto-close timeout
*/
autoClose?: number;
}
/**
* SSE message format
*/
interface EventStreamMessage {
/**
* Event name
*/
event?: string;
/**
* Event data
*/
data: string;
/**
* Event ID
*/
id?: string;
/**
* Retry interval
*/
retry?: number;
}/**
* WebSocket peer connection
*/
interface WSPeer {
/**
* Send message to peer
*/
send(message: string | ArrayBuffer): void;
/**
* Close connection
*/
close(code?: number, reason?: string): void;
/**
* Connection URL
*/
url: URL;
/**
* Connection context
*/
context: any;
/**
* Ready state
*/
readyState: number;
}
/**
* WebSocket message
*/
interface WSMessage {
/**
* Get message as text
*/
text(): string;
/**
* Get message as ArrayBuffer
*/
arrayBuffer(): ArrayBuffer;
/**
* Message type
*/
type: "text" | "binary";
}
/**
* WebSocket close details
*/
interface WSCloseDetails {
/**
* Close code
*/
code: number;
/**
* Close reason
*/
reason: string;
}/**
* Static serving options
*/
interface ServeStaticOptions {
/**
* Root directory to serve from
*/
root: string;
/**
* Index file names
*/
index?: string[];
/**
* How to handle dotfiles
*/
dotFiles?: "allow" | "deny" | "ignore";
/**
* Enable ETag generation
*/
etag?: boolean;
/**
* Enable Last-Modified header
*/
lastModified?: boolean;
/**
* Cache max age in seconds
*/
maxAge?: number;
/**
* Mark as immutable
*/
immutable?: boolean;
/**
* Allow fallthrough for missing files
*/
fallthrough?: boolean;
/**
* Redirect directories without trailing slash
*/
redirect?: boolean;
/**
* File extensions to try
*/
extensions?: string[];
}
/**
* Static asset metadata
*/
interface StaticAssetMeta {
/**
* File path
*/
path: string;
/**
* File size
*/
size: number;
/**
* MIME type
*/
type: string;
/**
* Last modified date
*/
mtime: Date;
/**
* ETag value
*/
etag?: string;
}