He
HeliumTS
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";
3
4const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);
5
6export const stripeWebhook = defineHTTPRequest("POST", "/webhooks/stripe", async (req, ctx) => {
7 const body = await req.text();
8 const signature = req.headers["stripe-signature"] as string;
9
10 try {
11 const event = stripe.webhooks.constructEvent(
12 body,
13 signature,
14 process.env.STRIPE_WEBHOOK_SECRET!
15 );
16
17 // Handle event
18 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 }
34
35 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";
3
4const openai = new OpenAI({
5 apiKey: process.env.OPENAI_API_KEY,
6});
7
8export const chatCompletionStream = defineHTTPRequest("POST", "/api/chat/stream", async (req, ctx) => {
9 const { message } = (await req.json()) as { message: string };
10
11 if (!message) {
12 return new Response(JSON.stringify({ error: "Message is required" }), {
13 status: 400,
14 headers: { "Content-Type": "application/json" },
15 });
16 }
17
18 try {
19 const stream = await openai.chat.completions.create({
20 model: "gpt-4",
21 messages: [{ role: "user", content: message }],
22 stream: true,
23 });
24
25 // Create a ReadableStream from the OpenAI stream
26 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 format
33 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 });
44
45 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);
55
56 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 stream
2async 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 });
8
9 const reader = response.body?.getReader();
10 const decoder = new TextDecoder();
11
12 while (reader) {
13 const { done, value } = await reader.read();
14 if (done) break;
15
16 const text = decoder.decode(value);
17 const lines = text.split("\n");
18
19 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 UI
23 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";
3
4export const auth = betterAuth({
5 database: {
6 // Your database configuration
7 },
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});
22
23// Catch-all handler for all auth routes
24export 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";
3
4const GITHUB_WEBHOOK_SECRET = process.env.GITHUB_WEBHOOK_SECRET!;
5
6function 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}
11
12export 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;
16
17 // Verify signature
18 if (!verifySignature(body, signature)) {
19 return new Response(JSON.stringify({ error: "Invalid signature" }), {
20 status: 401,
21 headers: { "Content-Type": "application/json" },
22 });
23 }
24
25 const payload = JSON.parse(body);
26
27 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 }
39
40 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";
4
5export const uploadFile = defineHTTPRequest("POST", "/api/upload", async (req, ctx) => {
6 const webRequest = await req.toWebRequest();
7 const formData = await webRequest.formData();
8
9 const file = formData.get("file") as File | null;
10
11 if (!file) {
12 return new Response(JSON.stringify({ error: "No file provided" }), {
13 status: 400,
14 headers: { "Content-Type": "application/json" },
15 });
16 }
17
18 // Validate file type
19 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 }
26
27 // 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 }
35
36 // Save file
37 const uploadDir = join(process.cwd(), "uploads");
38 await mkdir(uploadDir, { recursive: true });
39
40 const filename = `${Date.now()}-${file.name}`;
41 const filepath = join(uploadDir, filename);
42
43 const buffer = Buffer.from(await file.arrayBuffer());
44 await writeFile(filepath, buffer);
45
46 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";
2
3interface Todo {
4 id: string;
5 title: string;
6 completed: boolean;
7}
8
9// In-memory store (use a database in production)
10const todos = new Map<string, Todo>();
11
12// GET /api/todos - List all todos
13export const listTodos = defineHTTPRequest("GET", "/api/todos", async (req, ctx) => {
14 return { todos: Array.from(todos.values()) };
15});
16
17// GET /api/todos/:id - Get a single todo
18export const getTodo = defineHTTPRequest("GET", "/api/todos/:id", async (req, ctx) => {
19 const todo = todos.get(req.params.id);
20
21 if (!todo) {
22 return new Response(JSON.stringify({ error: "Todo not found" }), {
23 status: 404,
24 headers: { "Content-Type": "application/json" },
25 });
26 }
27
28 return { todo };
29});
30
31// POST /api/todos - Create a new todo
32export const createTodo = defineHTTPRequest("POST", "/api/todos", async (req, ctx) => {
33 const { title } = await req.json();
34
35 if (!title) {
36 return new Response(JSON.stringify({ error: "Title is required" }), {
37 status: 400,
38 headers: { "Content-Type": "application/json" },
39 });
40 }
41
42 const todo: Todo = {
43 id: crypto.randomUUID(),
44 title,
45 completed: false,
46 };
47
48 todos.set(todo.id, todo);
49
50 return new Response(JSON.stringify({ todo }), {
51 status: 201,
52 headers: { "Content-Type": "application/json" },
53 });
54});
55
56// PATCH /api/todos/:id - Update a todo
57export const updateTodo = defineHTTPRequest("PATCH", "/api/todos/:id", async (req, ctx) => {
58 const todo = todos.get(req.params.id);
59
60 if (!todo) {
61 return new Response(JSON.stringify({ error: "Todo not found" }), {
62 status: 404,
63 headers: { "Content-Type": "application/json" },
64 });
65 }
66
67 const updates = await req.json();
68 const updated = { ...todo, ...updates };
69 todos.set(todo.id, updated);
70
71 return { todo: updated };
72});
73
74// DELETE /api/todos/:id - Delete a todo
75export const deleteTodo = defineHTTPRequest("DELETE", "/api/todos/:id", async (req, ctx) => {
76 const existed = todos.delete(req.params.id);
77
78 if (!existed) {
79 return new Response(JSON.stringify({ error: "Todo not found" }), {
80 status: 404,
81 headers: { "Content-Type": "application/json" },
82 });
83 }
84
85 return new Response(null, { status: 204 });
86});

Server-Sent Events (SSE)

Real-time updates using SSE:

1import { defineHTTPRequest } from "heliumts/server";
2
3// Store connected clients
4const clients = new Set<ReadableStreamDefaultController>();
5
6// SSE endpoint - clients connect here
7export const sseEndpoint = defineHTTPRequest("GET", "/api/events", async (req, ctx) => {
8 const stream = new ReadableStream({
9 start(controller) {
10 clients.add(controller);
11
12 // Send initial connection message
13 controller.enqueue("data: connected\n\n");
14
15 // Cleanup on disconnect
16 req.signal?.addEventListener("abort", () => {
17 clients.delete(controller);
18 });
19 },
20 cancel() {
21 // Client disconnected
22 },
23 });
24
25 return new Response(stream, {
26 headers: {
27 "Content-Type": "text/event-stream",
28 "Cache-Control": "no-cache",
29 Connection: "keep-alive",
30 },
31 });
32});
33
34// Broadcast to all connected clients
35function broadcast(event: string, data: unknown) {
36 const message = `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
37
38 for (const client of clients) {
39 try {
40 client.enqueue(message);
41 } catch {
42 clients.delete(client);
43 }
44 }
45}
46
47// Trigger events from other endpoints
48export const createNotification = defineHTTPRequest("POST", "/api/notifications", async (req, ctx) => {
49 const { message } = await req.json();
50
51 broadcast("notification", { message, timestamp: Date.now() });
52
53 return { sent: true };
54});

Rate Limiting

Simple rate limiting example:

1import { defineHTTPRequest } from "heliumts/server";
2
3// Simple in-memory rate limiter
4const rateLimits = new Map<string, { count: number; resetTime: number }>();
5
6function checkRateLimit(ip: string, limit: number, windowMs: number): boolean {
7 const now = Date.now();
8 const record = rateLimits.get(ip);
9
10 if (!record || now > record.resetTime) {
11 rateLimits.set(ip, { count: 1, resetTime: now + windowMs });
12 return true;
13 }
14
15 if (record.count >= limit) {
16 return false;
17 }
18
19 record.count++;
20 return true;
21}
22
23export const rateLimitedEndpoint = defineHTTPRequest("POST", "/api/limited", async (req, ctx) => {
24 const ip = req.headers["x-forwarded-for"] as string || "unknown";
25
26 // 10 requests per minute
27 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 }
36
37 return { success: true };
38});