• Blog
  • Docs
  • Pricing
  • We’re hiring!
Log inSign up
mattspieg

mattspieg

sg-luma-events

Public
Like
sg-luma-events
Home
Code
10
RAILWAY_MIGRATION.md
README.md
deno-server.ts
deploy-to-railway.md
H
luma-proxy.ts
main.tsx
node-server.js
package.json
railway.toml
test-local.sh
Branches
1
Pull requests
Remixes
History
Environment variables
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.
Sign up now
Code
/
deploy-to-railway.md
Code
/
deploy-to-railway.md
Search
6/3/2025
deploy-to-railway.md

πŸš‚ Deploy Luma CORS Proxy to Railway

Complete Setup Guide (5 minutes)

This guide contains all the code and configuration files you need to deploy your own Luma CORS proxy to Railway.

1. Choose Your Runtime

Option A: Deno (Recommended)

  • Modern TypeScript runtime
  • No package management needed
  • Files needed: deno-server.ts + railway.toml

Option B: Node.js

  • Traditional Node.js with Express
  • Standard npm ecosystem
  • Files needed: node-server.js + package.json

πŸ“ Required Files

Option A: Deno Files

File: deno-server.ts

import { serve } from "https://deno.land/std@0.208.0/http/server.ts"; /** * Railway-ready Deno server for Luma API CORS Proxy * * This proxy solves CORS issues when accessing Luma API from frontend applications */ 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}`); console.log(`πŸ“‘ Ready to proxy requests to Luma API`); serve(handleRequest, { port });

File: railway.toml

[build] builder = "nixpacks" [deploy] startCommand = "deno run --allow-net --allow-env deno-server.ts" [environments.production] variables = {} [environments.staging] variables = {}

Option B: Node.js Files

File: node-server.js

const express = require('express'); const app = express(); /** * Railway-ready Node.js server for Luma API CORS Proxy * * This proxy solves CORS issues when accessing Luma API from frontend applications */ // CORS middleware app.use((req, res, next) => { 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(); }); // Main proxy endpoint app.get('*', async (req, res) => { try { // Only allow GET requests (OPTIONS handled above) if (req.method !== 'GET') { return res.status(405).send('Method not allowed'); } // Extract query parameters from the incoming request const searchParams = new URLSearchParams(req.query); // 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 = process.env.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.getTime() - dateB.getTime(); // 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.getTime() - dateA.getTime(); // 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 proper status code res.status(lumaResponse.status).json(filteredData); } catch (error) { console.error('Proxy error:', error); res.status(500).json({ error: 'Failed to fetch from Luma API', message: error instanceof Error ? error.message : 'Unknown error' }); } }); // Health check endpoint app.get('/health', (req, res) => { res.json({ status: 'ok', service: 'luma-cors-proxy' }); }); // Start the server const port = process.env.PORT || 8000; app.listen(port, () => { console.log(`πŸš€ Luma CORS Proxy running on port ${port}`); console.log(`πŸ“‘ Ready to proxy requests to Luma API`); console.log(`πŸ”— Health check: http://localhost:${port}/health`); });

File: package.json

{ "name": "luma-cors-proxy", "version": "1.0.0", "description": "CORS proxy for Luma API - Railway deployment", "main": "node-server.js", "scripts": { "start": "node node-server.js", "dev": "node node-server.js" }, "dependencies": { "express": "^4.18.2" }, "engines": { "node": ">=18" }, "keywords": ["cors", "proxy", "luma", "api", "railway"], "author": "Your Name", "license": "MIT" }

πŸš€ Deployment Steps

2. Create GitHub Repository

# Create a new repository with your chosen files mkdir luma-cors-proxy cd luma-cors-proxy # For Deno deployment (recommended): # Copy the deno-server.ts and railway.toml content above into files # OR for Node.js deployment: # Copy the node-server.js and package.json content above into files # Initialize git and push git init git add . git commit -m "Initial commit - Luma CORS Proxy for Railway" git remote add origin https://github.com/yourusername/luma-cors-proxy.git git push -u origin main

3. Deploy to Railway

  1. Go to railway.app
  2. Sign up/Login (can use GitHub)
  3. Click "Deploy from GitHub repo"
  4. Select your repository
  5. Railway auto-detects your runtime and deploys!

4. Configure Environment (Optional)

In Railway dashboard:

  • Go to your project
  • Click "Variables" tab
  • Add: LUMA_API_KEY = secret-FMPoZlwNgVJ0qkCSn2EIHpTUA

5. Get Your URL

After deployment, Railway gives you a URL like:

https://luma-cors-proxy-production.up.railway.app

6. Test Your Deployment

# Test basic functionality curl https://your-app.up.railway.app # Test next3 filter curl "https://your-app.up.railway.app?next3=true" # Test with calendar ID curl "https://your-app.up.railway.app?calendar_id=cal-0pgAb0xbjL529WF&next3=true"

7. Update Your Frontend Code

Replace your API calls with the new Railway URL:

// Use your new Railway proxy fetch('https://your-app.up.railway.app') .then(response => response.json()) .then(data => { console.log('Luma events:', data); // Use your event data here });

πŸŽ‰ You're Done!

Your CORS proxy is now running on Railway with:

  • βœ… Serverless architecture
  • βœ… Custom domain support
  • βœ… Auto-scaling
  • βœ… Environment variables
  • βœ… Git-based deployments
  • βœ… Free tier available

πŸ”§ Troubleshooting

Deployment fails?

  • Check Railway logs in dashboard
  • Verify file names match the runtime choice
  • Ensure all files are committed to git

Proxy not working?

  • Test the health endpoint: /health (Node.js version)
  • Check environment variables are set
  • Verify the proxy returns expected JSON data

CORS issues?

  • Verify the proxy is returning proper CORS headers
  • Test with a simple curl command first
  • Check browser network tab for error details

πŸš€ Next Steps

  1. Custom Domain: Add your own domain in Railway dashboard
  2. Monitoring: Set up Railway's built-in monitoring
  3. Scaling: Configure auto-scaling if needed
  4. Security: Consider rate limiting for production use

Your Luma CORS proxy is ready to handle all your event data needs! 🎯

FeaturesVersion controlCode intelligenceCLIMCP
Use cases
TeamsAI agentsSlackGTM
DocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Open Source Pledge
Terms of usePrivacy policyAbuse contact
Β© 2026 Val Town, Inc.