Do you remember the classic Snake from the old Nokias? Now imagine it with neon lights, synthesized music, explosive particles, and most importantly: connected to the cloud.
Web game development has experienced a renaissance thanks to the power of HTML5 Canvas and Serverless tools. In the past, if you wanted to save your players’ scores to create a global leaderboard, you needed to set up a server, configure a REST API, manage databases, and pay for hosting. Today, thanks to Backend-as-a-Service (BaaS), we can do it in minutes.
In this tutorial, we are going to build “Neon Snake”, an aesthetic and functional web arcade game that uses Supabase (an Open Source alternative to Firebase) to persist data in PostgreSQL.

🛠️ The Tech Stack
We’re going to keep it simple but powerful. No heavy frameworks, just the purity of the web:
- Frontend: HTML5 Canvas + Vanilla JavaScript (ES6+).
- Styles: Tailwind CSS (via CDN) for the user interface.
- Backend: Supabase (PostgreSQL + Realtime).
- Audio: Web Audio API for code-generated sound effects.
Step 1: Setting up the Backend with Supabase
Before writing a single line of JavaScript, we need our brain in the cloud. Supabase offers us a real PostgreSQL database with an automatically generated API.
- Create a free account at supabase.com.
- Create a new project called
NeonSnake. - Go to the Table Editor section and create a new table called
snake_scores.
The Magic of SQL
To streamline the process, Supabase allows you to execute SQL commands directly. Go to the SQL Editor and paste the following code. This will create the table structure and configure the security policies (RLS - Row Level Security) so your game can read and write data publicly.
-- 1. Create the scores table
create table snake_scores (
id bigint generated by default as identity primary key,
username text not null,
score integer not null,
created_at timestamp with time zone default timezone('utc'::text, now()) not null
);
-- 2. Enable Row Level Security (RLS)
alter table snake_scores enable row level security;
-- 3. Policy to allow anyone to view the ranking (Public Read)
create policy "Ver ranking publico"
on snake_scores for select
using ( true );
-- 4. Policy to allow anyone to save their record (Public Write)
create policy "Guardar puntuacion"
on snake_scores for insert
with check ( true );
Security Note: In a commercial game, we would use full user authentication. For this arcade-style demo, we allow public (anonymous) access to reduce friction so anyone can play and log their record instantly.
Finally, go to Settings > API and copy your Project URL and your anon public key. You will need them shortly.
Step 2: The Cyberpunk Frontend (HTML5 + Canvas)
Our game will live in a single index.html file. We will use Tailwind CSS for the floating menus (Game Over, Start Screen) and the <canvas> element to render the game at 60 FPS.
The basic structure is as follows:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Neon Snake 2077</title>
<script src="[https://cdn.tailwindcss.com](https://cdn.tailwindcss.com)"></script>
<style>
@import url('[https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap](https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap)');
body { font-family: 'Orbitron', sans-serif; background-color: #050505; }
/* Neon glow effect for the canvas */
canvas { box-shadow: 0 0 20px #0ff; border: 2px solid #0ff; }
</style>
</head>
<body class="h-screen w-full flex items-center justify-center overflow-hidden bg-gray-900">
<div class="relative">
<canvas id="gameCanvas" width="400" height="400"></canvas>
<div id="gameOverMenu" class="hidden absolute inset-0 bg-black/90 flex flex-col items-center justify-center z-10">
<h2 class="text-4xl text-red-500 font-bold mb-4 drop-shadow-[0_0_10px_rgba(239,68,68,0.8)]">SYSTEM FAILURE</h2>
<input type="text" id="usernameInput" placeholder="Enter ID..." class="bg-gray-800 text-cyan-400 p-2 rounded border border-cyan-500 mb-4 text-center outline-none">
<button id="saveBtn" class="bg-cyan-600 hover:bg-cyan-500 text-white font-bold py-2 px-4 rounded shadow-[0_0_15px_#06b6d4] transition-all">
UPLOAD DATA
</button>
<div id="leaderboard" class="mt-6 text-sm text-gray-300 w-64 text-left h-40 overflow-y-auto custom-scrollbar"></div>
</div>
</div>
<script type="module">
import { createClient } from '[https://cdn.jsdelivr.net/npm/@supabase/supabase-js/+esm](https://cdn.jsdelivr.net/npm/@supabase/supabase-js/+esm)'
// ... Game Logic here (See step 3) ...
</script>
</body>
</html>
Game Logic: Beyond Eating Apples
To give it that “Pro” touch and differentiate it from a basic tutorial, we implement several key mechanics in JavaScript:
- Animation Loop: Using
requestAnimationFrameto maintain fluidity at 60 FPS. - Particle System: An array of objects that spawn at the “food” coordinates when eaten and fade gradually, simulating a data explosion.
- Dynamic Firewalls: From level 5 onwards, we generate static obstacles that the player must dodge, progressively increasing the difficulty.
- Hybrid Controls:
- PC: We listen for
keydownevents for directional arrows. - Mobile: We capture
touchstartandtouchendto calculate the gesture direction (swipe) and move the snake.
- PC: We listen for
Step 3: Connecting the Dots (Integration with Supabase)
Here is where the Backend-as-a-Service magic happens. We are going to connect our Canvas to the PostgreSQL database. Inside our <script type="module"> tag:
1. Client Initialization
const supabaseUrl = 'YOUR_SUPABASE_PROJECT_URL';
const supabaseKey = 'YOUR_SUPABASE_ANON_KEY';
const supabase = createClient(supabaseUrl, supabaseKey);
2. The “Game Over” Flow
When the player loses and hits “UPLOAD DATA”, we need to ensure the data is saved before showing the leaderboard. We will use async/await to control this asynchronous flow.
async function saveScore(username, score) {
// 1. Save the score
const { error } = await supabase
.from('snake_scores')
.insert([{ username: username, score: score }]);
if (error) {
console.error('Error uploading data:', error);
alert('Connection error with the central server.');
return;
}
// 2. Immediately after, update the leaderboard visually
fetchLeaderboard();
}
3. Fetching the Ranking (Select)
We retrieve the Top 20 sorted by score in descending order to show who rules the server.
async function fetchLeaderboard() {
const { data: scores, error } = await supabase
.from('snake_scores')
.select('username, score')
.order('score', { ascending: false }) // Descending order
.limit(20); // Only the top 20
if (error) console.error('Error fetching leaderboard:', error);
// Render in the DOM
const list = document.getElementById('leaderboard');
list.innerHTML = '<h3 class="text-cyan-400 border-b border-cyan-500 mb-2 font-bold">TOP HACKERS</h3>';
scores.forEach((entry, index) => {
// Highlight top 3 with different colors if you want
const colorClass = index < 3 ? 'text-yellow-400' : 'text-gray-300';
list.innerHTML += `
<div class="flex justify-between py-1 border-b border-gray-800">
<span class="${colorClass}">#${index + 1} ${entry.username}</span>
<span class="text-cyan-300 font-mono">${entry.score}</span>
</div>`;
});
}
Bonus: Realtime ⚡
Do you want the ranking to update on everyone’s screen if someone breaks the record at that precise moment? Supabase makes this trivial with its subscriptions:
// Subscribe to changes in the 'snake_scores' table
supabase
.channel('public:snake_scores')
.on('postgres_changes',
{ event: 'INSERT', schema: 'public', table: 'snake_scores' },
(payload) => {
console.log('New record detected on the network!', payload);
fetchLeaderboard(); // Automatically refresh the list for everyone
}
)
.subscribe();
The Final Result
By putting it all together, you get a fluid and highly competitive user experience:
- The user plays frantically dodging neon “Firewalls”.
- Upon crashing, the game stops with a visual “glitch” effect.
- They enter their name (e.g., “Neo”).
- Upon clicking send, in milliseconds, their name appears on the leaderboard.
- If their friend is playing on another mobile at the same time, they will see “Neo’s” name appear on their screen without reloading the page.
And once created… all that remains is to enjoy and play 🐍😊
Conclusion
We have moved from a simple static canvas to a Fullstack application in real-time without touching a traditional backend server (Node.js, PHP, Python) or managing infrastructure. The combination of creativity in game development with the power of BaaS tools like Supabase opens a world of possibilities for frontend developers.
The next step? I invite you to clone the project and improve it: add GitHub authentication to avoid duplicate names, create snake skins that unlock with high scores, or implement a multiplayer mode using Supabase presence channels.
The code is yours! 🐍💻
Did you like this tutorial? Share it on your networks with your High Scores and don’t forget to check the official Supabase documentation to dive deeper.