Today is a full-stack local event discovery app with a Tinder-style swipe interface. It surfaces events happening in the next 24 hours, learns from user preferences and interactions, and lets users manage liked events in a calendar-style view.
- Frontend (
React + TypeScript)Onboarding: preference questionnaire (sports, arts, volunteering, culture)Home: swipe interface for next-24-hour eventsMy Events: calendar-style list for liked/attended events with modal details- Local session stored in
localStorage(today.session.id)
- Backend (
Node.js + Express)- REST API for preferences, discovery, interactions, and saved events
- Validation using
zod - Recommendation scoring from preferences + interaction history
- Database (
SQLitevia Nodenode:sqlite)- Local DB file:
server/today.db(requires Node 22.13+ for the API) - Seeded sample events on first boot
- Local DB file:
today-app/
server/
src/
db.js
index.js
src/
api/
client.ts
components/
EventCalendar.tsx
EventDetailsModal.tsx
Layout.tsx
PreferenceQuiz.tsx
SwipeCard.tsx
lib/
session.ts
pages/
HomePage.tsx
MyEventsPage.tsx
OnboardingPage.tsx
types/
models.ts
assets/
logo.png
App.tsx
main.tsx
index.cssBase URL: http://localhost:4000/api
GET /health- Health check.
GET /debug/providers- Shows whether Ticketmaster/Eventbrite/Google Places keys are loaded in the server process.
GET /preferences/:sessionId- Fetch saved categories for a user session.
POST /preferences- Save onboarding categories.
POST /onboarding/responses- Save yes/no questionnaire answers and mapped categories.
POST /events/sync- Fetch and normalize external events based on coordinates, user preferences, and radius (typically a ~48h window).
GET /events/discover?sessionId=...- Returns events in roughly the next 48 hours not already liked/disliked.
- Uses scoring from preferences and historical interactions.
POST /interactions- Store swipe interaction (
like,dislike) per event.
- Store swipe interaction (
POST /attendance- Save post-event attendance status (
attended,missed).
- Save post-event attendance status (
GET /events/:eventId- Event detail endpoint.
GET /my-events?sessionId=...- Returns liked/attended events for calendar view.
id TEXT PRIMARY KEYtitle TEXT NOT NULLdescription TEXT NOT NULLstarts_at TEXT NOT NULLends_at TEXT NOT NULLcost REALimage_url TEXT NOT NULLcategory TEXT NOT NULLlocation TEXT NOT NULLaddress TEXT
session_id TEXT NOT NULLcategory TEXT NOT NULLweight INTEGER NOT NULL DEFAULT 1updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMPPRIMARY KEY (session_id, category)
id INTEGER PRIMARY KEY AUTOINCREMENTsession_id TEXT NOT NULLevent_id TEXT NOT NULLaction TEXT NOT NULL CHECK(action IN ('like', 'dislike', 'attended'))created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMPUNIQUE(session_id, event_id, action)
- Only events with
starts_atbetween now and +24 hours are shown in discovery. - Events already swiped (
like/dislike) are removed from future discovery results. - Ranking prioritizes:
- matched onboarding preferences
- positive interaction history (
like,attended) - near-term start time
- penalties for historical dislikes
curl -X POST http://localhost:4000/api/preferences \
-H "Content-Type: application/json" \
-d '{
"sessionId": "today_123",
"categories": ["sports", "arts"]
}'curl "http://localhost:4000/api/events/discover?sessionId=today_123"curl -X POST http://localhost:4000/api/interactions \
-H "Content-Type: application/json" \
-d '{
"sessionId": "today_123",
"eventId": "event-id-here",
"action": "like"
}'curl -X POST http://localhost:4000/api/interactions \
-H "Content-Type: application/json" \
-d '{
"sessionId": "today_123",
"eventId": "event-id-here",
"action": "attended"
}'The app supports:
TICKETMASTER_API_KEYEVENTBRITE_API_TOKENGOOGLE_PLACES_API_KEY(optional enrichment for address/image quality)
- Copy
.env.exampleto.env.local. - Fill in your keys.
- Run the app normally.
server/src/env.js auto-loads .env.local at startup, and .gitignore already ignores *.local.
$env:TICKETMASTER_API_KEY="your_ticketmaster_key"
$env:EVENTBRITE_API_TOKEN="your_eventbrite_token"
$env:GOOGLE_PLACES_API_KEY="your_google_places_key"Without keys, the app gracefully falls back to local seeded events.
- Install dependencies:
npm install- Run frontend + backend together:
npm run dev- Open frontend:
Backend runs at:
npm run dev:server- Change radius in
Settings(miles/km). - Home swipe feed and My Events calendar re-sync automatically when radius is saved.
Open:
You should see true for providers you configured in .env.local.
The Vercel project is only the static frontend. The API must run on a Node host (Render, Railway, Fly, etc.).
- Push this repo to GitHub (if it is not already).
- In Render: New → Blueprint, connect the repo, and apply
render.yaml. - After the service is created, open it → Environment → add (optional but needed for real event data):
TICKETMASTER_API_KEY,EVENTBRITE_API_TOKEN,GOOGLE_PLACES_API_KEY(same as.env.locallocally).
- Wait for deploy; open
https://<your-service>.onrender.com/api/healthand confirm JSON{"ok":true}. - In Vercel → your project → Environment variables → add
VITE_API_BASE=https://<your-service>.onrender.com/api(your real host). - Redeploy the Vercel app so the build picks up
VITE_API_BASE.
SQLite on Render’s free tier is stored on an ephemeral disk (data can reset on restarts). For a durable DB, add a Render disk and set TODAY_DB_PATH to a file on that mount (e.g. /mnt/data/today.db).
-
Build failed / “out of memory” / very long install
Ensure the service usesnpm ci --omit=dev(seerender.yaml). A full install pulls in Vite/React and can exhaust the free builder. -
“Application failed to respond” or health check
Free web services sleep after idle; first request can take ~30–60s. Open/api/healthagain after a short wait. -
Blueprint / YAML errors
render.yamlmust be at the repository root of the GitHub repo you connect (same folder aspackage.json). -
Node version
The API usesnode:sqlite(built into Node). Use Node 22.13+ locally (nvm install 22/fnm install 22) andNODE_VERSION=22on Render (seerender.yaml). -
Still stuck
In Render → your service → Logs; copy the last 30–40 lines of the failing build or deploy log—that usually states the exact error (e.g. wrong Node version, missing env).
| Variable | Purpose |
|---|---|
PORT |
Set automatically on most hosts (defaults to 4000 locally). |
TODAY_DB_PATH |
Optional absolute path to the SQLite file (default: server/today.db under the project root). |
- No full authentication is required; a local session ID is used.
src/assets/logo.pngis used in the application header.- UI is responsive and composed of reusable components.