๐ต ์ธ๋ ์์ ์คํธ๋ฆฌ๋ฐ ํ๋ ์ด๋ฆฌ์คํธ - ์ฅ๋ฅด๋ณ ํ๋ ์ด์ & ์ข์์
indie-music-playlist-curator
๋ฌธ์ ์ค๋ช
๐ง ์ธ๋ ์์ ์คํธ๋ฆฌ๋ฐ ํ๋ ์ด๋ฆฌ์คํธ ๋ง๋ค๊ธฐ
์ธ๋ ๋ฎค์ง์ ๋ค์ ์์ ์ ์ฅ๋ฅด๋ณ๋ก ํ๋ ์ด์ ํ๊ณ , ์ฌ์ฉ์๊ฐ ๋ง์์ ๋๋ ๊ณก์ ์ข์์๋ฅผ ๋๋ฅผ ์ ์๋ ๊ฐ๋จํ ์์ ํ๋ ์ด๋ฆฌ์คํธ ํ๋ซํผ์ ๋๋ค. Laravel API ๋ฐฑ์๋์ React ํ๋ก ํธ์๋๋ก ๊ตฌ์ฑ๋ ํ์คํ ํ๋ก์ ํธ๋ก, ํ๋ก๊ทธ๋๋ฐ์ ์ฒ์ ์ ํ๋ ๋ถ๋ AI ๋๊ตฌ์ ํจ๊ป๋ผ๋ฉด ์ถฉ๋ถํ ์์ฑํ ์ ์์ต๋๋ค.
โจ ํ์ ๊ธฐ๋ฅ
- ์ฅ๋ฅด๋ณ ์์ ๋ชฉ๋ก: ์ธ๋ํ, ์ธ๋๋ก, ํฌํฌ, ์ผ๋ ํธ๋ก๋ ๋ฑ ์ฅ๋ฅด ํํฐ๋ง
- ๊ณก ์ ๋ณด ์นด๋: ์ํฐ์คํธ๋ช , ๊ณก ์ ๋ชฉ, ์จ๋ฒ ์ปค๋ฒ, ์ฌ์ ์๊ฐ ํ์
- ์ข์์ ๊ธฐ๋ฅ: ๋ง์์ ๋๋ ๊ณก์ ํํธ ๋ฒํผ ํด๋ฆญ
- ์ฌ์ ํ์ ์นด์ดํฐ: ๊ณก์ด ์ฌ์๋ ๋๋ง๋ค ์นด์ดํธ ์ฆ๊ฐ
- ๋ฐ์ํ ๋์์ธ: ๋ชจ๋ฐ์ผ/๋ฐ์คํฌํฑ ๋ชจ๋ ์ต์ ํ
๐ ์ ํ ์ถ๊ฐ ๊ธฐ๋ฅ (๋ณด๋์ค)
- ์ต๊ทผ ์ฌ์ ๋ชฉ๋ก ํ์
- ์ธ๊ธฐ ๊ณก Top 10 ์ฐจํธ
- ๊ฒ์ ๊ธฐ๋ฅ (์ํฐ์คํธ๋ช , ๊ณก ์ ๋ชฉ)
- ๋คํฌ ๋ชจ๋ ํ ๊ธ
- ๊ณก ๊ณต์ ๋ฒํผ (SNS ๋งํฌ ๋ณต์ฌ)
๐ ๏ธ ๊ธฐ์ ์คํ
- ๋ฐฑ์๋: Laravel 13.x (RESTful API, SQLite)
- ํ๋ก ํธ์๋: React 19 (Vite, Tailwind CSS)
- ์ํ๊ด๋ฆฌ: useState๋ง ์ฌ์ฉ (๋ณต์กํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ถํ์)
- API ํต์ : fetch ๋๋ axios
์ด ํ๋ก์ ํธ๋ฅผ ํตํด Laravel API ์ค๊ณ, React ์ปดํฌ๋ํธ ๊ตฌ์กฐ, ํ๋ก ํธ-๋ฐฑ์๋ ๋ฐ์ดํฐ ์ฐ๋์ ๊ธฐ์ด๋ฅผ ๋ฐฐ์ธ ์ ์์ต๋๋ค.
์ ์ฝ ์กฐ๊ฑด
- โข Laravel 13.x ์ฌ์ฉ (๊ตฌ๋ฒ์ ์ ๋ ๊ธ์ง)
- โข React 19 ์ฌ์ฉ (Vite ๊ธฐ๋ฐ ํ๋ก์ ํธ)
- โข ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ SQLite ์ฌ์ฉ (๊ฐ๋จํ ์ค์ )
- โข ๋ณต์กํ ์ํ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ ๊ธ์ง (useState๋ง ์ฌ์ฉ)
- โข WebSocket/์ค์๊ฐ ํต์ ๊ธฐ๋ฅ ์์ (HTTP ๊ธฐ๋ฐ๋ง)
- โข Tailwind CSS๋ก ์คํ์ผ๋ง
- โข ๋ฐ์ํ ๋์์ธ ํ์
- โข ํ๋ก ํธ์๋๋ Vite๋ก ๋ณ๋ ํ๋ก์ ํธ ๊ตฌ์ฑ
- โข CORS ์ค์ ํฌํจ
- โข API๋ RESTful ๊ตฌ์กฐ ์ค์
ํ๋กฌํํธ ๋ ์ํผ
์๋ ํ๋กฌํํธ๋ฅผ ์์๋๋ก AI์๊ฒ ๋ณด๋ด๋ฉด ๋ฉ๋๋ค. ๋ณต์ฌ ๋ฒํผ์ ๋๋ฌ ๋ฐ๋ก ์ฌ์ฉํ์ธ์.
mkdir indie-music-playlist-curator && cd indie-music-playlist-curator ์ด ํด๋์์ ํ๋ก์ ํธ๋ฅผ ์์ํฉ๋๋ค. ## Laravel 13.x API ๋ฐฑ์๋ ํ๋ก์ ํธ ์์ฑ ๋ค์ ์๊ตฌ์ฌํญ์ ๋ง์ถฐ Laravel ํ๋ก์ ํธ๋ฅผ ์์ฑํ๊ณ ์ด๊ธฐ ์ค์ ์ ์๋ฃํด์ฃผ์ธ์: 1. **Laravel 13.x ํ๋ก์ ํธ ์์ฑ** - `backend` ํด๋์ Laravel 13.x ์ค์น - Composer ์ต์ ๋ฒ์ ์ฌ์ฉ 2. **๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค์ ** - SQLite ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ฌ์ฉ (.env ํ์ผ ์์ ) - `database/database.sqlite` ํ์ผ ์์ฑ 3. **๋ง์ด๊ทธ๋ ์ด์ ์์ฑ** - `songs` ํ ์ด๋ธ ์์ฑ - ์ปฌ๋ผ: id, title (๊ณก ์ ๋ชฉ), artist (์ํฐ์คํธ๋ช ), genre (์ฅ๋ฅด), album_cover (์จ๋ฒ ์ปค๋ฒ URL), duration (์ฌ์ ์๊ฐ, ์ด ๋จ์), play_count (์ฌ์ ํ์, ๊ธฐ๋ณธ๊ฐ 0), likes_count (์ข์์ ์, ๊ธฐ๋ณธ๊ฐ 0), timestamps 4. **์๋(Seeder) ์์ฑ** - 10๊ฐ ์ด์์ ์ํ ์ธ๋ ์์ ๋ฐ์ดํฐ ์ถ๊ฐ - ์ฅ๋ฅด: ์ธ๋ํ, ์ธ๋๋ก, ํฌํฌ, ์ผ๋ ํธ๋ก๋ ๋ฑ ๋ค์ํ๊ฒ - ์จ๋ฒ ์ปค๋ฒ๋ placeholder ์ด๋ฏธ์ง URL ์ฌ์ฉ (์: https://via.placeholder.com/300) 5. **CORS ์ค์ ** - `config/cors.php` ์ค์ (React ์ฑ๊ณผ ํต์ ๊ฐ๋ฅํ๋๋ก) - `php artisan config:publish cors` ๋ช ๋ น ์คํ ํ ์ค์ ๋ช ๋ น์ด์ ์ฝ๋๋ฅผ ๋ชจ๋ ์ ๊ณตํด์ฃผ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: Laravel 13.x ๋ฐฑ์๋ ํ๋ก์ ํธ๊ฐ ์์ฑ๋๊ณ , SQLite ๋ฐ์ดํฐ๋ฒ ์ด์ค์ songs ํ ์ด๋ธ์ด ๋ง์ด๊ทธ๋ ์ด์ ๋๋ฉฐ, ์ํ ์์ ๋ฐ์ดํฐ๊ฐ ์๋ฉ๋ฉ๋๋ค. CORS ์ค์ ์ด ์๋ฃ๋์ด React ์ฑ๊ณผ ํต์ ํ ์ค๋น๊ฐ ๋ฉ๋๋ค.
## Laravel API ๋ผ์ฐํธ ๋ฐ ์ปจํธ๋กค๋ฌ ๊ตฌํ
RESTful API๋ฅผ ๊ตฌํํด์ฃผ์ธ์:
1. **์ปจํธ๋กค๋ฌ ์์ฑ**
- `SongController` ์์ฑ (Resource Controller)
2. **API ๋ผ์ฐํธ ์ ์** (`routes/api.php`)
- `GET /api/songs` - ๋ชจ๋ ๊ณก ๋ชฉ๋ก ์กฐํ (์ฅ๋ฅด ํํฐ๋ง ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ ์ง์)
- `GET /api/songs/{id}` - ํน์ ๊ณก ์์ธ ์กฐํ
- `POST /api/songs/{id}/like` - ์ข์์ ์ฆ๊ฐ
- `POST /api/songs/{id}/play` - ์ฌ์ ํ์ ์ฆ๊ฐ
3. **์ปจํธ๋กค๋ฌ ๋ฉ์๋ ๊ตฌํ**
- `index()`: ๋ชจ๋ ๊ณก ๋ฐํ, ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ `genre`๋ก ํํฐ๋ง ๊ฐ๋ฅ
- `show($id)`: ํน์ ๊ณก ์์ธ ์ ๋ณด ๋ฐํ
- `like($id)`: likes_count 1 ์ฆ๊ฐ ํ ์
๋ฐ์ดํธ๋ ๊ณก ์ ๋ณด ๋ฐํ
- `play($id)`: play_count 1 ์ฆ๊ฐ ํ ์
๋ฐ์ดํธ๋ ๊ณก ์ ๋ณด ๋ฐํ
4. **JSON ์๋ต ํ์**
- ์ฑ๊ณต ์: `{ "success": true, "data": {...} }`
- ์คํจ ์: `{ "success": false, "message": "์๋ฌ ๋ฉ์์ง" }`
๋ชจ๋ ์ฝ๋๋ฅผ ์์ฑํด์ฃผ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: Laravel API ์๋ํฌ์ธํธ๊ฐ ์์ฑ๋๊ณ , Postman์ด๋ curl๋ก ํ ์คํธ ๊ฐ๋ฅํ ์ํ๊ฐ ๋ฉ๋๋ค. ์ฅ๋ฅด ํํฐ๋ง, ์ข์์, ์ฌ์ ํ์ ์ฆ๊ฐ ๊ธฐ๋ฅ์ด ๋ชจ๋ ๋์ํฉ๋๋ค.
## React 19 ํ๋ก ํธ์๋ ํ๋ก์ ํธ ์์ฑ `frontend` ํด๋์ React ํ๋ก์ ํธ๋ฅผ ์์ฑํ๊ณ ์ด๊ธฐ ์ค์ ์ ์๋ฃํด์ฃผ์ธ์: 1. **Vite๋ก React 19 ํ๋ก์ ํธ ์์ฑ** - `npm create vite@latest frontend -- --template react` - React 19 ๋ฒ์ ํ์ธ 2. **Tailwind CSS ์ค์น ๋ฐ ์ค์ ** - Tailwind CSS ์ต์ ๋ฒ์ ์ค์น - `tailwind.config.js` ์ค์ - `src/index.css`์ Tailwind directives ์ถ๊ฐ 3. **Axios ์ค์น** - `npm install axios` - API ํต์ ์ฉ axios ์ธ์คํด์ค ์์ฑ (`src/api/axios.js`) - baseURL: `http://localhost:8000/api` 4. **๊ธฐ๋ณธ ํด๋ ๊ตฌ์กฐ ์์ฑ** - `src/components/` - ์ฌ์ฌ์ฉ ์ปดํฌ๋ํธ - `src/pages/` - ํ์ด์ง ์ปดํฌ๋ํธ - `src/api/` - API ํธ์ถ ํจ์ 5. **๊ฐ๋ฐ ์๋ฒ ์คํ ํ์ธ** - `npm run dev` ์คํํ์ฌ ๊ธฐ๋ณธ ํ๋ฉด ํ์ธ ๋ชจ๋ ๋ช ๋ น์ด์ ์ค์ ํ์ผ ์ฝ๋๋ฅผ ์ ๊ณตํด์ฃผ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: React 19 ํ๋ก ํธ์๋ ํ๋ก์ ํธ๊ฐ ์์ฑ๋๊ณ , Tailwind CSS์ Axios๊ฐ ์ค์น๋ฉ๋๋ค. ๊ฐ๋ฐ ์๋ฒ๊ฐ ์คํ๋์ด ๊ธฐ๋ณธ ํ๋ฉด์ ํ์ธํ ์ ์์ต๋๋ค.
## ์์ ๋ชฉ๋ก ํ์ด์ง ๊ตฌํ (React ์ปดํฌ๋ํธ) ๋ฉ์ธ ํ์ด์ง์ ์์ ๋ชฉ๋ก์ ํ์ํ๋ ๊ธฐ๋ฅ์ ๊ตฌํํด์ฃผ์ธ์: 1. **API ํธ์ถ ํจ์ ์์ฑ** (`src/api/songs.js`) - `fetchSongs(genre = '')` - ๊ณก ๋ชฉ๋ก ์กฐํ (์ฅ๋ฅด ํํฐ๋ง ์ต์ ) - `likeSong(id)` - ์ข์์ ์ฆ๊ฐ - `playSong(id)` - ์ฌ์ ํ์ ์ฆ๊ฐ 2. **SongCard ์ปดํฌ๋ํธ** (`src/components/SongCard.jsx`) - Props: song ๊ฐ์ฒด (id, title, artist, genre, album_cover, duration, play_count, likes_count) - ์จ๋ฒ ์ปค๋ฒ ์ด๋ฏธ์ง ํ์ - ์ํฐ์คํธ๋ช , ๊ณก ์ ๋ชฉ ํ์ - ์ฅ๋ฅด ๋ฐฐ์ง (Tailwind๋ก ์คํ์ผ๋ง) - ์ฌ์ ์๊ฐ ํ์ (์ด โ ๋ถ:์ด ํ์ ๋ณํ) - ์ข์์ ๋ฒํผ (ํํธ ์์ด์ฝ, ํด๋ฆญ ์ likes_count ์ฆ๊ฐ) - ์ฌ์ ๋ฒํผ (์ฌ์ ์์ด์ฝ, ํด๋ฆญ ์ play_count ์ฆ๊ฐ) - ํธ๋ฒ ์ ๊ทธ๋ฆผ์ ํจ๊ณผ 3. **MusicList ํ์ด์ง** (`src/pages/MusicList.jsx`) - useState๋ก songs ๋ฐฐ์ด ์ํ ๊ด๋ฆฌ - useState๋ก selectedGenre ์ํ ๊ด๋ฆฌ - useEffect๋ก ์ปดํฌ๋ํธ ๋ง์ดํธ ์ fetchSongs ํธ์ถ - ์ฅ๋ฅด ํํฐ ๋ฒํผ๋ค (์ ์ฒด, ์ธ๋ํ, ์ธ๋๋ก, ํฌํฌ, ์ผ๋ ํธ๋ก๋) - ํํฐ ๋ฒํผ ํด๋ฆญ ์ selectedGenre ๋ณ๊ฒฝ ๋ฐ API ์ฌํธ์ถ - SongCard ์ปดํฌ๋ํธ๋ฅผ ๊ทธ๋ฆฌ๋ ๋ ์ด์์์ผ๋ก ๋ฐฐ์น (๋ฐ์ํ: ๋ชจ๋ฐ์ผ 1์ด, ํ๋ธ๋ฆฟ 2์ด, ๋ฐ์คํฌํฑ 3์ด) - ๋ก๋ฉ ์ํ ํ์ - ์๋ฌ ์ฒ๋ฆฌ 4. **App.jsx ์์ ** - MusicList ํ์ด์ง๋ฅผ ๋ฉ์ธ์ผ๋ก ๋ ๋๋ง - ํค๋ ์ถ๊ฐ (๋ก๊ณ , ์ ๋ชฉ) ๋ชจ๋ ์ปดํฌ๋ํธ ์ฝ๋๋ฅผ ์์ฑํด์ฃผ์ธ์. Tailwind CSS๋ก ์คํ์ผ๋งํ๊ณ , ๋ฐ์ํ ๋์์ธ์ ์ ์ฉํ์ธ์.
๊ธฐ๋ ๊ฒฐ๊ณผ: React ํ๋ก ํธ์๋์์ Laravel API๋ฅผ ํธ์ถํ์ฌ ์์ ๋ชฉ๋ก์ ํ๋ฉด์ ํ์ํฉ๋๋ค. ์ฅ๋ฅด ํํฐ๋ง, ์ข์์, ์ฌ์ ๋ฒํผ์ด ๋ชจ๋ ๋์ํ๋ฉฐ, ๋ฐ์ํ ๊ทธ๋ฆฌ๋ ๋ ์ด์์์ด ์ ์ฉ๋ฉ๋๋ค.
์ ์ถ๋ ์ํ (0)
๋ก๊ทธ์ธ ํ ์ ์ถ์์ง ์ ์ถ๋ ์ํ์ด ์์ต๋๋ค. ์ฒซ ๋ฒ์งธ ๋์ ์๊ฐ ๋์ด๋ณด์ธ์!