This repo features a fully working app that allows you to connect to room with your friends and play music trivia.
The very good documentation provided below describes communication contract between frontend app (Next.js) and backend server (Django). Very useful stuff, wasn't very fun to write.
To run the whole shit in dev mode, use command provided below in the root directory.
docker compose -f dev.docker-compose.yml up --buildDockerfiles and docker-compose for production will come in the future lol. They will also include redis and postgress because why not.
Containers can communicate with each other using:
http://backend:8000
ws://backend:8000
However, frontend requests come from browser, not from the container itself, so they should be just directed here:
http://localhost:8000
ws://localhost:8000
docker compose up -d
docker compose exec backend uv run python manage.py makemigrations game
docker compose exec backend uv run python manage.py migrate
All requests must include credentials: "include" so the browser sends the session cookie.
GET /spotify/login
Redirects the browser through the Spotify OAuth flow. After the user grants access, the backend creates a session and redirects to the frontend (FRONTEND_URL). On failure the frontend receives FRONTEND_URL/?auth_error=<reason>.
GET /spotify/profile
Returns the logged-in user's Spotify profile. Use this to check whether the user is authenticated — 401 means not logged in.
{
"ok": true,
"profile": {
"id": "spotify_user_id",
"display_name": "John Doe",
"profile_image_url": "https://i.scdn.co/image/ab6775700000ee85example"
}
}GET /spotify/logout
Clears the session and deletes the user.
{
"ok": true,
"spotify_disconnected": true
}GET /spotify/playlists
Returns a list of user's playlists.
{
"ok": true,
"playlists": [
{
"id": "1dsvBXJqJ0yReZKc5bOqAS",
"name": "✨𝔟𝔢𝔰𝔱 𝔬𝔣 𝔱𝔥𝔢 𝔟𝔢𝔰𝔱✨",
"image_url": "https://image-cdn-fa.spotifycdn.com/image/ab67706c0000da842e428a59a6d4906dc2da460d"
},
{
"id": "6vsFj5ydNbZYiNr4FHZAlV",
"name": "🌴 ɢʀօօօօօʋɛ",
"image_url": "https://image-cdn-ak.spotifycdn.com/image/ab67706c0000da84d183e7962c861b203b0f23b0"
},
]
}All websocket string messages should follow this format:
{
"type": "namespace.event",
"data": {}, // object containing additional fields depending on the message type
"ok": true // required only in backend -> frontend messages
}Non-string messages are chunks of audio stream.
room.join
Joins the room with code and nickname.
{"name": "nickname", "code": "abcd123"}namemust be between 3-20 letters, numbers + underscores.codemust consist of 6 letters.
room.create
Creates a new room, sets a playlist, amount of rounds and adds a user as a host.
{"name": "nickname", "playlist_id": "", "rounds" : 10}namemust be between 3-20 letters, numbers + underscores.- 1 <=
rounds<= 20 playlistidmust be a valid spotify playlist.
room.ready
Toggles the calling player's ready status. Broadcasts an updated room.updated state to all members.
{}room.start
Starts the game for all players.
{}- user must be a host.
song.guess
Sends a guess.
{"guess": ""}guessmust be less that 100 characters.
room.updated
Returned to a client with current game state. The State object will have always have the same fields
{
"state": {
"code": "ABCDEF",
"host_name": "alice",
"members": ["alice", "bob", "charlie"],
"ready_players": ["alice", "bob"],
"scoreboard": {
"alice": 15,
"bob": 10,
"charlie": 5
},
"playlist_name": "Top Hits 2025",
"playlist_img": "https://i.scdn.co/image/ab67616d0000b273example",
"started": true,
"rounds": 5,
"current_round": 2
}
}room.started
Returned to all clients after host sent room.start. The game will begin shortly after receiving this
song.correct
Returned to a client after host sent song.guess if the guess was correct. Contains info about amount of earned points and metadata of currently playing song to display.
{
"points" : 233,
"title": "Example Title",
"artist": "Artist Name",
"img": "https://i.scdn.co/image/ab67616d0000b273example"
}song.incorrect
Returned to a client after host sent song.guess if the guess was incorrect.
round.started
Returned to all clients signifying beginning of the next round. Packets with music will be delivered shortly. Round clock timer can be started from this point (~30 seconds?).
round.ended
Returned to all clients signifying ending of the current round. Round end clock timer can be started from this point (~10 seconds?). Contains info about amount of earned points this round and metadata of the song that was playing this round to display.
{
"points" : 0,
"title": "Example Title",
"artist": "Artist Name",
"img": "https://i.scdn.co/image/ab67616d0000b273example"
}room.ended
Returned to all clients when game ends. Returns the state like room.updated allowing to display final scores. Client will be disconnected by the server from the websocket in the next couple seconds.
{
"state": {
"code": "ABCDEF",
"host_name": "alice",
"members": ["alice", "bob", "charlie"],
"scoreboard": {
"alice": 15,
"bob": 10,
"charlie": 5
},
"playlist_name": "Top Hits 2025",
"playlist_img": "https://i.scdn.co/image/ab67616d0000b273example",
"started": true,
"rounds": 5,
"current_round": 2
}
}If error occurs, the ok field should be set to false. Error field should be added and set to object that follows this format:
"error": {
"code": "ROOM_NOT_FOUND",
"message": "Room does not exist"
}