imposter.fm is a podcast website + admin CMS built with TanStack Start.
- Serves a public podcast site (homepage latest episode, archive, detail, embed widget)
- Generates a podcast RSS feed at
/rss.xml - Streams audio through
/api/stream/:episodeIdwith range request support - Generates per-episode Open Graph images (
/api/og/episode/:slug) - Provides a persistent mini player that continues across route navigation
- Saves listener playback preferences (
speed,volume) in local storage - Tracks listens with a lightweight endpoint (
POST /api/listen) - Includes admin auth + episode management (create, edit, publish, delete)
- Supports both uploading audio files and recording audio directly in-app
- Supports admin password changes (
/admin/password)
- TanStack Start + TanStack Router + React 19
- TanStack Query + tRPC
- Drizzle ORM + PostgreSQL
- Better Auth (email/password)
- Tailwind CSS v4
- Biome (lint/format)
- Optional PostHog analytics
- Bun
- PostgreSQL
Copy .env.example to .env.local and fill values.
Required:
DATABASE_URL- PostgreSQL connection stringBETTER_AUTH_SECRET- Better Auth secret
Recommended in production:
BETTER_AUTH_BASE_URL- canonical auth base URL (for redirects/callbacks)BETTER_AUTH_TRUSTED_ORIGINS- comma-separated trusted origins
Runtime/platform provided:
PORT- server port (defaults to3000locally)NODE_ENV- environment mode (developmentorproduction)
Optional admin bootstrap values:
ADMIN_EMAIL(defaultadmin@imposter.fm)ADMIN_PASSWORD(defaultchangeme123)ADMIN_NAME(defaultAdmin)
Optional frontend/site values:
VITE_SITE_URL- canonical site URL used for metadata/share links
Optional analytics values:
VITE_POSTHOG_KEY- PostHog project key (phc_...)VITE_POSTHOG_HOST- defaults tohttps://us.i.posthog.com
Optional production object storage values (S3 or R2 compatible):
S3_BUCKET_NAMEorR2_BUCKET_NAMES3_ENDPOINTorR2_ENDPOINTS3_REGION(defaults toauto)S3_ACCESS_KEY_IDorR2_ACCESS_KEY_IDS3_SECRET_ACCESS_KEYorR2_SECRET_ACCESS_KEYS3_FORCE_PATH_STYLE(truefor providers like Railway bucket storage)
Storage behavior:
- Development uses local uploads at
./uploadsvia/api/upload - Production uses S3/R2 signed upload URLs
uploads/is gitignored and removed from git history
- Install dependencies:
bun install-
Create
.env.localwith required values. -
Push schema to database:
bun run db:push- Create an initial admin user:
bunx tsx scripts/create-admin.ts- Start dev server:
bun run devApp URL: http://localhost:3000
Admin login: /login
bun run dev
bun run build
bun run start
bun run preview
bun run test
bun run lint
bun run format
bun run check
bun run db:generate
bun run db:migrate
bun run db:push
bun run db:pull
bun run db:studioPublic:
//episodes/episodes/:slug/embed/:slug/rss.xml
Admin:
/login/admin/admin/episodes/admin/episodes/new/admin/episodes/:id/edit/admin/password
API:
/api/stream/:episodeId/api/listen/api/upload(development local upload target)/api/og/episode/:slug/api/trpc/*
- RSS feed falls back to defaults if no podcast settings row exists.
- PostHog initialization is skipped when key is missing or placeholder (
phc_xxx). - Upload signing only accepts audio MIME types and validated storage keys.
