Note:
HeliumTS is under pre-beta and active development. Expect bugs and breaking changes. If you find any issues, please report them in our GitHub
A stable release is planned for early December 2025.
HTTP Handler Examples
Stripe Webhook
Handle Stripe webhook events with signature verification:
1import { defineHTTPRequest } from "heliumts/server";2import Stripe from "stripe";34const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);56export const stripeWebhook = defineHTTPRequest("POST", "/webhooks/stripe", async (req, ctx) => {7 const body = await req.text();8 const signature = req.headers["stripe-signature"] as string;910 try {11 const event = stripe.webhooks.constructEvent(12 body,13 signature,14 process.env.STRIPE_WEBHOOK_SECRET!15 );1617 // Handle event18 switch (event.type) {19 case "payment_intent.succeeded":20 const paymentIntent = event.data.object;21 await handlePaymentSuccess(paymentIntent);22 break;23 case "customer.subscription.created":24 const subscription = event.data.object;25 await handleSubscriptionCreated(subscription);26 break;27 case "customer.subscription.deleted":28 await handleSubscriptionCanceled(event.data.object);29 break;30 case "invoice.payment_failed":31 await handlePaymentFailed(event.data.object);32 break;33 }3435 return { received: true };36 } catch (err) {37 console.error("Webhook error:", err);38 return new Response(JSON.stringify({ error: "Invalid signature" }), {39 status: 400,40 headers: { "Content-Type": "application/json" },41 });42 }43});
Important:
- Always verify the webhook signature
- Use
req.text()to get the raw body (required for signature verification) - Return a 200 status quickly to avoid Stripe retries
OpenAI Streaming
Stream OpenAI chat completions to the client:
1import { defineHTTPRequest } from "heliumts/server";2import OpenAI from "openai";34const openai = new OpenAI({5 apiKey: process.env.OPENAI_API_KEY,6});78export const chatCompletionStream = defineHTTPRequest("POST", "/api/chat/stream", async (req, ctx) => {9 const { message } = (await req.json()) as { message: string };1011 if (!message) {12 return new Response(JSON.stringify({ error: "Message is required" }), {13 status: 400,14 headers: { "Content-Type": "application/json" },15 });16 }1718 try {19 const stream = await openai.chat.completions.create({20 model: "gpt-4",21 messages: [{ role: "user", content: message }],22 stream: true,23 });2425 // Create a ReadableStream from the OpenAI stream26 const readableStream = new ReadableStream({27 async start(controller) {28 try {29 for await (const chunk of stream) {30 const content = chunk.choices[0]?.delta?.content || "";31 if (content) {32 // Send as SSE format33 const data = `data: ${JSON.stringify({ content })}\n\n`;34 controller.enqueue(new TextEncoder().encode(data));35 }36 }37 controller.enqueue(new TextEncoder().encode("data: [DONE]\n\n"));38 controller.close();39 } catch (error) {40 controller.error(error);41 }42 },43 });4445 return new Response(readableStream, {46 status: 200,47 headers: {48 "Content-Type": "text/event-stream",49 "Cache-Control": "no-cache",50 Connection: "keep-alive",51 },52 });53 } catch (error) {54 console.error("OpenAI API error:", error);5556 return new Response(57 JSON.stringify({ error: "Failed to get completion" }),58 {59 status: 500,60 headers: { "Content-Type": "application/json" },61 }62 );63 }64});
Client-Side Consumption
1// React component consuming the stream2async function streamChat(message: string) {3 const response = await fetch("/api/chat/stream", {4 method: "POST",5 headers: { "Content-Type": "application/json" },6 body: JSON.stringify({ message }),7 });89 const reader = response.body?.getReader();10 const decoder = new TextDecoder();1112 while (reader) {13 const { done, value } = await reader.read();14 if (done) break;1516 const text = decoder.decode(value);17 const lines = text.split("\n");1819 for (const line of lines) {20 if (line.startsWith("data: ") && line !== "data: [DONE]") {21 const json = JSON.parse(line.slice(6));22 // Append content to UI23 console.log(json.content);24 }25 }26 }27}
Authentication Handler (Better Auth)
Integrate with Better Auth or similar authentication libraries:
1import { defineHTTPRequest } from "heliumts/server";2import { betterAuth } from "better-auth";34export const auth = betterAuth({5 database: {6 // Your database configuration7 },8 emailAndPassword: {9 enabled: true,10 },11 socialProviders: {12 google: {13 clientId: process.env.GOOGLE_CLIENT_ID!,14 clientSecret: process.env.GOOGLE_CLIENT_SECRET!,15 },16 github: {17 clientId: process.env.GITHUB_CLIENT_ID!,18 clientSecret: process.env.GITHUB_CLIENT_SECRET!,19 },20 },21});2223// Catch-all handler for all auth routes24export const authHandler = defineHTTPRequest("ALL", "/api/auth/*", async (req, ctx) => {25 const webRequest = await req.toWebRequest();26 return auth.handler(webRequest);27});
GitHub Webhook
Handle GitHub webhook events:
1import { defineHTTPRequest } from "heliumts/server";2import crypto from "crypto";34const GITHUB_WEBHOOK_SECRET = process.env.GITHUB_WEBHOOK_SECRET!;56function verifySignature(payload: string, signature: string): boolean {7 const hmac = crypto.createHmac("sha256", GITHUB_WEBHOOK_SECRET);8 const digest = "sha256=" + hmac.update(payload).digest("hex");9 return crypto.timingSafeEqual(Buffer.from(digest), Buffer.from(signature));10}1112export const githubWebhook = defineHTTPRequest("POST", "/webhooks/github", async (req, ctx) => {13 const body = await req.text();14 const signature = req.headers["x-hub-signature-256"] as string;15 const event = req.headers["x-github-event"] as string;1617 // Verify signature18 if (!verifySignature(body, signature)) {19 return new Response(JSON.stringify({ error: "Invalid signature" }), {20 status: 401,21 headers: { "Content-Type": "application/json" },22 });23 }2425 const payload = JSON.parse(body);2627 switch (event) {28 case "push":29 console.log(`Push to ${payload.repository.full_name}`);30 // Trigger CI/CD, update deployment, etc.31 break;32 case "pull_request":33 console.log(`PR ${payload.action}: ${payload.pull_request.title}`);34 break;35 case "issues":36 console.log(`Issue ${payload.action}: ${payload.issue.title}`);37 break;38 }3940 return { received: true };41});
File Upload
Handle file uploads with form data:
1import { defineHTTPRequest } from "heliumts/server";2import { writeFile, mkdir } from "fs/promises";3import { join } from "path";45export const uploadFile = defineHTTPRequest("POST", "/api/upload", async (req, ctx) => {6 const webRequest = await req.toWebRequest();7 const formData = await webRequest.formData();89 const file = formData.get("file") as File | null;1011 if (!file) {12 return new Response(JSON.stringify({ error: "No file provided" }), {13 status: 400,14 headers: { "Content-Type": "application/json" },15 });16 }1718 // Validate file type19 const allowedTypes = ["image/jpeg", "image/png", "image/gif", "application/pdf"];20 if (!allowedTypes.includes(file.type)) {21 return new Response(JSON.stringify({ error: "Invalid file type" }), {22 status: 400,23 headers: { "Content-Type": "application/json" },24 });25 }2627 // Validate file size (10MB max)28 const maxSize = 10 * 1024 * 1024;29 if (file.size > maxSize) {30 return new Response(JSON.stringify({ error: "File too large" }), {31 status: 400,32 headers: { "Content-Type": "application/json" },33 });34 }3536 // Save file37 const uploadDir = join(process.cwd(), "uploads");38 await mkdir(uploadDir, { recursive: true });3940 const filename = `${Date.now()}-${file.name}`;41 const filepath = join(uploadDir, filename);4243 const buffer = Buffer.from(await file.arrayBuffer());44 await writeFile(filepath, buffer);4546 return {47 success: true,48 filename,49 size: file.size,50 type: file.type,51 };52});
REST API CRUD
A complete REST API example:
1import { defineHTTPRequest } from "heliumts/server";23interface Todo {4 id: string;5 title: string;6 completed: boolean;7}89// In-memory store (use a database in production)10const todos = new Map<string, Todo>();1112// GET /api/todos - List all todos13export const listTodos = defineHTTPRequest("GET", "/api/todos", async (req, ctx) => {14 return { todos: Array.from(todos.values()) };15});1617// GET /api/todos/:id - Get a single todo18export const getTodo = defineHTTPRequest("GET", "/api/todos/:id", async (req, ctx) => {19 const todo = todos.get(req.params.id);2021 if (!todo) {22 return new Response(JSON.stringify({ error: "Todo not found" }), {23 status: 404,24 headers: { "Content-Type": "application/json" },25 });26 }2728 return { todo };29});3031// POST /api/todos - Create a new todo32export const createTodo = defineHTTPRequest("POST", "/api/todos", async (req, ctx) => {33 const { title } = await req.json();3435 if (!title) {36 return new Response(JSON.stringify({ error: "Title is required" }), {37 status: 400,38 headers: { "Content-Type": "application/json" },39 });40 }4142 const todo: Todo = {43 id: crypto.randomUUID(),44 title,45 completed: false,46 };4748 todos.set(todo.id, todo);4950 return new Response(JSON.stringify({ todo }), {51 status: 201,52 headers: { "Content-Type": "application/json" },53 });54});5556// PATCH /api/todos/:id - Update a todo57export const updateTodo = defineHTTPRequest("PATCH", "/api/todos/:id", async (req, ctx) => {58 const todo = todos.get(req.params.id);5960 if (!todo) {61 return new Response(JSON.stringify({ error: "Todo not found" }), {62 status: 404,63 headers: { "Content-Type": "application/json" },64 });65 }6667 const updates = await req.json();68 const updated = { ...todo, ...updates };69 todos.set(todo.id, updated);7071 return { todo: updated };72});7374// DELETE /api/todos/:id - Delete a todo75export const deleteTodo = defineHTTPRequest("DELETE", "/api/todos/:id", async (req, ctx) => {76 const existed = todos.delete(req.params.id);7778 if (!existed) {79 return new Response(JSON.stringify({ error: "Todo not found" }), {80 status: 404,81 headers: { "Content-Type": "application/json" },82 });83 }8485 return new Response(null, { status: 204 });86});
Server-Sent Events (SSE)
Real-time updates using SSE:
1import { defineHTTPRequest } from "heliumts/server";23// Store connected clients4const clients = new Set<ReadableStreamDefaultController>();56// SSE endpoint - clients connect here7export const sseEndpoint = defineHTTPRequest("GET", "/api/events", async (req, ctx) => {8 const stream = new ReadableStream({9 start(controller) {10 clients.add(controller);1112 // Send initial connection message13 controller.enqueue("data: connected\n\n");1415 // Cleanup on disconnect16 req.signal?.addEventListener("abort", () => {17 clients.delete(controller);18 });19 },20 cancel() {21 // Client disconnected22 },23 });2425 return new Response(stream, {26 headers: {27 "Content-Type": "text/event-stream",28 "Cache-Control": "no-cache",29 Connection: "keep-alive",30 },31 });32});3334// Broadcast to all connected clients35function broadcast(event: string, data: unknown) {36 const message = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;3738 for (const client of clients) {39 try {40 client.enqueue(message);41 } catch {42 clients.delete(client);43 }44 }45}4647// Trigger events from other endpoints48export const createNotification = defineHTTPRequest("POST", "/api/notifications", async (req, ctx) => {49 const { message } = await req.json();5051 broadcast("notification", { message, timestamp: Date.now() });5253 return { sent: true };54});
Rate Limiting
Simple rate limiting example:
1import { defineHTTPRequest } from "heliumts/server";23// Simple in-memory rate limiter4const rateLimits = new Map<string, { count: number; resetTime: number }>();56function checkRateLimit(ip: string, limit: number, windowMs: number): boolean {7 const now = Date.now();8 const record = rateLimits.get(ip);910 if (!record || now > record.resetTime) {11 rateLimits.set(ip, { count: 1, resetTime: now + windowMs });12 return true;13 }1415 if (record.count >= limit) {16 return false;17 }1819 record.count++;20 return true;21}2223export const rateLimitedEndpoint = defineHTTPRequest("POST", "/api/limited", async (req, ctx) => {24 const ip = req.headers["x-forwarded-for"] as string || "unknown";2526 // 10 requests per minute27 if (!checkRateLimit(ip, 10, 60 * 1000)) {28 return new Response(JSON.stringify({ error: "Rate limit exceeded" }), {29 status: 429,30 headers: {31 "Content-Type": "application/json",32 "Retry-After": "60",33 },34 });35 }3637 return { success: true };38});