sg-luma-events
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data β all from the browser, and deployed in milliseconds.
Viewing readonly version of main branch: v23View latest version
Your Luma CORS proxy is ready for Railway! Here are two deployment options:
import { serve } from "https://deno.land/std@0.208.0/http/server.ts";
// Your existing proxy logic from luma-proxy.ts
async function handleRequest(req: Request): Promise<Response> {
// Handle preflight OPTIONS request
if (req.method === 'OPTIONS') {
return new Response(null, {
status: 200,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Max-Age': '86400',
},
});
}
// Only allow GET requests
if (req.method !== 'GET') {
return new Response('Method not allowed', {
status: 405,
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'text/plain',
},
});
}
try {
// Extract query parameters from the incoming request
const url = new URL(req.url);
const searchParams = url.searchParams;
// Check if we should filter to next 3 events
const limitNext3 = searchParams.get('next3') === 'true';
// Build the Luma API URL with query parameters
const lumaUrl = new URL('https://api.lu.ma/public/v1/calendar/list-events');
// If we're filtering for next 3, optimize the API call
if (limitNext3) {
// Remove next3 from params to forward to Luma API
searchParams.delete('next3');
// Use correct Luma API parameter names from the documentation
lumaUrl.searchParams.set('pagination_limit', '100');
// Try to get events from a reasonable time range
// Use 'after' parameter with current date to get future events
const today = new Date();
const todayISO = today.toISOString();
lumaUrl.searchParams.set('after', todayISO);
// Sort by start date ascending to get earliest upcoming events first
lumaUrl.searchParams.set('sort_column', 'start_at');
lumaUrl.searchParams.set('sort_direction', 'asc');
}
// Forward all remaining query parameters to the Luma API
for (const [key, value] of searchParams.entries()) {
lumaUrl.searchParams.set(key, value);
}
// Get API key from environment variable (more secure) or fallback to hardcoded
const apiKey = Deno.env.get('LUMA_API_KEY') || 'secret-FMPoZlwNgVJ0qkCSn2EIHpTUA';
// Make the request to Luma API with the API key
const lumaResponse = await fetch(lumaUrl.toString(), {
method: 'GET',
headers: {
'x-luma-api-key': apiKey,
'Content-Type': 'application/json',
},
});
// Get the response data
const data = await lumaResponse.json();
let filteredData = data;
if (limitNext3 && data.entries && Array.isArray(data.entries)) {
const now = new Date();
// First, try to find upcoming events
let upcomingEvents = data.entries
.filter(entry => {
const eventStartTime = new Date(entry.event.start_at);
return eventStartTime > now; // Only future events
})
.sort((a, b) => {
const dateA = new Date(a.event.start_at);
const dateB = new Date(b.event.start_at);
return dateA - dateB; // Sort by date ascending (earliest first)
});
// If we have upcoming events, use them
if (upcomingEvents.length > 0) {
upcomingEvents = upcomingEvents.slice(0, 3);
filteredData = {
...data,
entries: upcomingEvents,
has_more: upcomingEvents.length === 3 && (data.entries.length > upcomingEvents.length || data.has_more)
};
} else {
// No upcoming events, fall back to most recent past events
const recentPastEvents = data.entries
.filter(entry => {
const eventStartTime = new Date(entry.event.start_at);
return eventStartTime <= now; // Only past events
})
.sort((a, b) => {
const dateA = new Date(a.event.start_at);
const dateB = new Date(b.event.start_at);
return dateB - dateA; // Sort by date descending (most recent first)
})
.slice(0, 3);
filteredData = {
...data,
entries: recentPastEvents,
has_more: false, // Since we're showing past events as fallback
_fallback: 'recent_past_events' // Indicator that we fell back to past events
};
}
}
// Return the data with CORS headers
return new Response(JSON.stringify(filteredData), {
status: lumaResponse.status,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'Content-Type': 'application/json',
},
});
} catch (error) {
console.error('Proxy error:', error);
return new Response(JSON.stringify({
error: 'Failed to fetch from Luma API',
message: error instanceof Error ? error.message : 'Unknown error'
}), {
status: 500,
headers: {
'Access-Control-Allow-Origin': '*',
'Content-Type': 'application/json',
},
});
}
}
// Start the server
const port = parseInt(Deno.env.get("PORT") || "8000");
console.log(`π Luma CORS Proxy running on port ${port}`);
serve(handleRequest, { port });
[build] builder = "nixpacks" [deploy] startCommand = "deno run --allow-net --allow-env server.ts"
{ "name": "luma-cors-proxy", "version": "1.0.0", "description": "CORS proxy for Luma API", "main": "server.js", "scripts": { "start": "node server.js", "dev": "node server.js" }, "dependencies": { "express": "^4.18.2" }, "engines": { "node": ">=18" } }
const express = require('express');
const app = express();
// Your proxy logic converted to Express
app.use((req, res, next) => {
// Handle CORS
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type');
if (req.method === 'OPTIONS') {
res.header('Access-Control-Max-Age', '86400');
return res.status(200).end();
}
next();
});
app.get('*', async (req, res) => {
try {
// Your existing proxy logic here, but using:
// - process.env.LUMA_API_KEY instead of Deno.env.get()
// - require('node-fetch') or built-in fetch for HTTP requests
const apiKey = process.env.LUMA_API_KEY || 'secret-FMPoZlwNgVJ0qkCSn2EIHpTUA';
// ... rest of your proxy logic
} catch (error) {
console.error('Proxy error:', error);
res.status(500).json({
error: 'Failed to fetch from Luma API',
message: error.message
});
}
});
const port = process.env.PORT || 8000;
app.listen(port, () => {
console.log(`π Luma CORS Proxy running on port ${port}`);
});
- Create a new GitHub repository with your chosen option files
- Go to railway.app and sign up/login
- Click "Deploy from GitHub repo"
- Select your repository
- Set environment variables (optional):
LUMA_API_KEY=secret-FMPoZlwNgVJ0qkCSn2EIHpTUA
- Deploy! Railway will auto-detect and deploy
Your proxy will be available at:
https://[your-app-name].up.railway.app
Update your Webflow code to use this new URL instead of the Val Town URL.
- β Free tier available
- β Auto-scaling
- β Custom domains
- β Environment variables
- β Git-based deployments
- β Built-in monitoring
- β Your client already uses it!
If you run into any issues:
- Check Railway logs in the dashboard
- Verify environment variables are set
- Test the proxy URL directly in browser
- Compare with your working Val Town version
Your migration should be seamless since it's just a simple stateless proxy! π