Welcome to Tunely 🎵 - a small music-streaming startup. The mobile team is about to ship the player screen, and they need the backend's Songs API ready. You've just been added to the repo.
The API is a mock: there are no audio files, no uploads and no authentication. Songs are just metadata from a fake catalog, and everything lives in memory (restart the server and it resets).
Part of the API was never written, and part of what was written is misbehaving. The project is split across several files like a real codebase, so expect to read, trace and fix across modules.
Your job has two parts:
- Part A - Build: implement 4 endpoints from scratch.
- Part B - Debug: QA filed several bug reports against endpoints that already exist. Find the root causes and fix them.
💡 A failing test does not always mean the handler under test is wrong. Read the failure message, then follow the request through routes → controllers → utils → db.
Data model
Song (never modified by the API)
{ "id": 1, "title": "Neon Horizon", "artist": "The Midnight Owls", "album": "City Lights", "durationSec": 215, "genre": "synthwave" }
Playlist is stored as { id, name, description, songIds }. A playlist detail response looks like:
{
"id": 1,
"name": "Chill Vibes",
"description": "Easy listening for slow afternoons",
"songs": [ /* full Song objects, in playlist order */ ],
"songCount": 8,
"totalDurationSec": 1681
}
Library is stored as { id, name, songIds }. A library detail response is { id, name, songs, songCount }.
Queue is a single global player queue (there are no users). Its response shape is:
{ "nowPlaying": { /* Song */ } , "upNext": [ /* Song, Song, ... */ ] }
nowPlaying is null when nothing is playing.
Seed data (restored by resetDb() before every test): playlist 1 "Chill Vibes" with songs 1-8, playlist 2 "Road Trip" with songs 9, 10, 11, library 1 "My Library" with songs 1, 2, and an empty queue.
Use the helpers in utils/ (formatters, finders, validators) and db.nextPlaylistId() / db.nextLibraryId() rather than reinventing them.
Error format: every error is JSON shaped like { "error": "message" }.
Part A - Build these endpoints (currently return 501 Not Implemented)
1. POST /api/playlists - create a playlist
Body: { "name": string, "description"?: string, "songIds"?: number[] }
name is required and must be a non-blank string, otherwise 400.
description defaults to "", songIds defaults to [] and the order of songIds is preserved.
songIds must be an array of existing song ids, otherwise 400 and nothing is created.
- Each new playlist gets a unique id from
db.nextPlaylistId().
- Success:
201 with the playlist detail shape.
2. DELETE /api/playlists/:id - delete a playlist
- Success:
204 with an empty body. The playlist is gone from GET /api/playlists and GET /api/playlists/:id returns 404.
- Unknown playlist:
404.
3. POST /api/queue/add - add to queue
Body: { "songId": number }
- Appends the song to the end of
upNext.
songId must be a positive integer, otherwise 400. An unknown song gives 404. In both cases the queue must stay unchanged.
- Success:
201 with the queue shape.
4. POST /api/libraries - create a library
Body: { "name": string }
name is required and must be a non-blank string, otherwise 400 and nothing is created.
- Each new library gets a unique id from
db.nextLibraryId() and starts empty.
- Success:
201 with the library detail shape.
Part B - Fix these bug reports
These endpoints already exist, but QA reports they don't behave according to the contract below. The reports only describe symptoms, so finding the cause is part of the task.
Expected behaviour (the contract)
PUT /api/playlists/:id - body { "name"?: string, "description"?: string }
- Only
name and description can change. id and songIds must never be changed by the request body.
- If
name is provided it must be a non-blank string, otherwise 400 and the playlist stays untouched.
- Success:
200 with the playlist detail. Unknown playlist: 404.
POST /api/playlists/:id/shuffle - no body
- Returns
200 with { "playlistId": number, "songs": Song[] }, containing every song of the playlist exactly once in a random order.
- The stored order of the playlist is not modified. Unknown playlist:
404.
POST /api/queue/play-next - body { "songId": number }
- Inserts the song at the front of
upNext, so the most recent play next plays first.
- Validation matches
/api/queue/add: 400 for an invalid songId, 404 for an unknown song.
- Success:
201 with the queue shape.
POST /api/libraries/:id/songs - body { "songId": number }
- Checks run in this order: library not found
404, invalid songId 400, song not found 404, song already in the library 409.
- Success:
201 with the library detail. A rejected request must leave the library unchanged.
Already implemented (should keep working)
Try it yourself
Start the server in the terminal with node index.js (it listens on port 3000) and poke at it with curl:
curl localhost:3000/api/songs?genre=rock
curl -X POST localhost:3000/api/queue/add -H "Content-Type: application/json" -d '{"songId": 3}'
curl localhost:3000/api/queue
Restart the server any time to reset the in-memory data.
Rules & tips
- Keep the response shapes and status codes exactly as described above - the tests check them.
- Don't add authentication, a real database or audio handling. Everything stays in memory.
- You may refactor and add helpers, but keep the existing route paths.
- Tests are grouped per resource. Start with the first failing test of a group and read its message carefully.
- Fixing a shared helper can turn several red tests green at once. 🟢