A full‑stack music generation app. Users describe a song (or provide lyrics/prompts), and the system generates audio and a cover image using an AI backend, stores them in S3, and manages tracks, categories, and publishing in a PostgreSQL database.
- Frontend: Next.js 15 (App Router), React 19, TypeScript, Tailwind CSS, Radix UI, Zustand
- Auth: Better Auth + Prisma adapter
- DB: Neon (PostgreSQL) via Prisma 6
- Jobs/Orchestration: Inngest
- Storage: AWS S3 (audio .wav and cover images .png)
- AI Backend: Python (Modal), PyTorch, Transformers, Diffusers, ACE‑Step pipeline, Qwen2‑7B, SDXL‑Turbo
./
├── backend/
│ ├── main.py # Modal app: model loading, endpoints, S3 uploads
│ ├── prompts.py # LLM prompt templates
│ └── requirements.txt # Python deps
└── frontend/
├── prisma/schema.prisma # Prisma models: User, Song, Category, Like, etc.
├── src/
│ ├── actions/ # Server Actions (generation, song ops)
│ ├── app/ # Next.js routes (auth, main, API)
│ ├── components/ # UI (create flow, track list, player UI)
│ ├── inngest/ # Inngest client + functions
│ ├── lib/ # auth, utils
│ ├── server/db.ts # Prisma client
│ └── stores/ # Zustand player store
├── next.config.js # Next.js config (remote image hosts)
├── src/env.js # Env validation (@t3-oss/env-nextjs)
└── package.json
- User signs in (Better Auth). Main routes require a session.
- User submits a request (full description OR prompt+lyrics OR prompt+described lyrics).
- A Server Action creates
Songrows with statusqueuedand sends an Inngest event. - Inngest function selects the appropriate Modal endpoint, sets
status=processing, calls the backend. - Backend generates audio and a cover image, uploads both to S3, returns S3 keys and detected categories.
- Inngest updates the
Songwiths3key,thumbnails3key, status, and connects categories; decrements user credits. - UI lists tracks; clicking a processed track fetches a presigned URL and plays/downloads it.
User: id, email, image, auth metadata,credits(default 100)Song: title,s3key,thumbnails3key,status,instrumental, prompt/lyrics fields, params,published,listenCount,categories,userIdCategory: name (unique), many‑to‑many with songsLike: composite key(userId, songId)- Auth tables:
Session,Account,Verification
Validated in frontend/src/env.js.
Required (server side):
BETTER_AUTH_SECRETBETTER_AUTH_URLDATABASE_URL(Neon PostgreSQL connection string)NODE_ENV(development|test|production)MODAL_KEY,MODAL_SECRET(Modal proxy auth)AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY_ID(credentials used for presigning)AWS_REGION(e.g.,eu-central-1— must match the bucket’s region)S3_BUCKET_NAME(no spaces)GENERATE_FROM_DESCRIPTIONGENERATE_FROM_DESCRIBED_LYRICSGENERATE_WITH_LYRICS
Notes:
- Bucket name must not include spaces or leading/trailing whitespace.
- The app presigns S3 GET URLs on the server using the above credentials.
cd frontend
npm install
npm run db:generate # prisma migrate dev (or adjust to your workflow)Ensure .env exists in frontend/ with all server variables above.
- Create a Neon database and set
DATABASE_URL(Neon provides PostgreSQL connection strings). - Run migrations:
cd frontend
npm run db:migrate # or: npm run db:push for non-migration dev flow- Create an S3 bucket and set
S3_BUCKET_NAMEandAWS_REGION. - Ensure the IAM user (access key/secret) has permissions for
s3:GetObjectands3:PutObjectfor that bucket.
- Install Modal CLI and authenticate.
- The Python service is defined in
backend/main.py. It uses Modal’s@app.clswith GPU and@modal.fastapi_endpointendpoints. - Expose endpoints and set their URLs in:
GENERATE_FROM_DESCRIPTIONGENERATE_FROM_DESCRIBED_LYRICSGENERATE_WITH_LYRICS
- The Inngest API route is served from Next at
src/app/api/inngest/route.ts. - Configure Inngest project as needed; the client id is
music-geninsrc/inngest/client.ts.
In one terminal (frontend):
cd frontend
npm run devThis starts Next.js. Sign in at /auth/sign-in.
Modal backend runs on Modal’s infra. Ensure your env variables point to the live endpoints. If running locally for testing, adapt the endpoints accordingly.
npm run dev— Start dev servernpm run build && npm start— Production build/startnpm run db:generate—prisma migrate devnpm run db:migrate—prisma migrate deploynpm run db:push— Push schema without migrations (dev only)npm run db:studio— Prisma Studionpm run check— Lint + typecheck
next.config.jsallows remote images. If usingnext/image, ensure the hostname matches your S3 URL pattern, e.g.**.s3.eu-central-1.amazonaws.com.- The app currently uses standard
<img>tags in the track list.
- Thumbnails don’t load / 400/403 on presigned URL
- Check for spaces in
S3_BUCKET_NAME. Trailing spaces produce InvalidBucketName and%20in the URL. - Ensure
AWS_REGIONmatches the bucket region exactly. - Verify presigner uses correct credentials:
accessKeyId = AWS_ACCESS_KEY_IDsecretAccessKey = AWS_SECRET_ACCESS_KEY_ID(variable name as defined in env)
- Keys must not start with
/. The app sanitizes leading slashes.
- Check for spaces in
- Image downloads but doesn’t render
- Set content type when uploading in backend:
s3_client.upload_file(image_output_path, bucket_name, image_s3_key, ExtraArgs={"ContentType": "image/png"})
- Regenerate after fixing metadata (existing objects keep old metadata).
- Set content type when uploading in backend:
- Audio playback fails
- The presigned audio URL comes from
getPlayUrl. Confirms3keyexists and user is authorized.
- The presigned audio URL comes from
- No credits
- Inngest sets
status = "no credits"whenUser.credits <= 0.
- Inngest sets
- Job failures
- Inngest
onFailuresetsstatus = "failed". Check Modal logs and Next server logs.
- Inngest
- Server Actions and Inngest calls run server-side and use server env vars.
- Presigned URLs expire after 1 hour (
expiresIn: 3600). Adjust as needed. - Ensure Modal endpoints require proxy auth and env secrets are set.
MIT (or your preferred license).