Side project · 2026 Projet perso · 2026مشروع شخصي · 2026

Realtime Leaderboard

A leaderboard service for any game. Games send scores through a REST API, and everyone watching sees the rankings change live, with no refresh, even across several servers. Un service de classement pour n'importe quel jeu. Les jeux envoient leurs scores par une API REST, et tous ceux qui regardent voient le classement bouger en direct, sans recharger, même sur plusieurs serveurs.خدمة ترتيب لأي لعبة. ترسل الألعاب نتائجها عبر REST API، ويرى كل المتابعين الترتيب يتغيّر مباشرةً، دون إعادة تحميل، حتى عبر عدة خوادم.

RoleRôleالدور
Solo project: from a front-end prototype (2025) to a full service with a database, an API and tests (2026)Projet solo : d'un prototype front (2025) à un service complet avec base de données, API et tests (2026)مشروع فردي: من نموذج أولي للواجهة (2025) إلى خدمة كاملة بقاعدة بيانات و API واختبارات (2026)
HostingHébergementالاستضافة
Render (Frankfurt) and Neon PostgreSQLRender (Francfort) et Neon PostgreSQLRender (فرانكفورت) و Neon PostgreSQL
StackStackالتقنيات
Next.js 14 · TypeScript · PostgreSQL 16 · Prisma · Server-Sent Events · Zod · Vitest · Tailwind CSS
LinksLiensالروابط
Live demoDémo en ligneالعرض التجريبي ↗ · Code ↗ (free hosting: up to a minute to wake up)(hébergement gratuit : jusqu'à une minute pour démarrer)(استضافة مجانية: قد يستغرق التشغيل حتى دقيقة)

01The challengeLe défiالتحدي

Showing a ranking is easy. Keeping it live for every viewer is not: when a score arrives on one server, viewers connected to another server must see it too, right away, without each browser polling the API every second. Afficher un classement, c'est facile. Le garder en direct pour tout le monde, beaucoup moins : quand un score arrive sur un serveur, ceux qui sont connectés à un autre serveur doivent le voir aussi, tout de suite, sans que chaque navigateur interroge l'API toutes les secondes.عرض ترتيب أمر سهل. إبقاؤه مباشراً للجميع أصعب بكثير: عندما تصل نتيجة إلى خادم، يجب أن يراها فوراً المتصلون بخادم آخر أيضاً، دون أن يستعلم كل متصفح الـ API كل ثانية.

02How it stays liveComment ça reste en directكيف يبقى الترتيب مباشراً

GameJeuاللعبةPOST /scores · API key
→
Server AServeur Aالخادم AINSERT + NOTIFY
→
PostgreSQLLISTEN / NOTIFY
→
Servers A, B…Serveurs A, B…الخوادم A، B…LISTEN
→
ViewersSpectateursالمتابعونServer-Sent Events

When a score is saved, the server publishes an event with PostgreSQL NOTIFY. Every server keeps one LISTEN connection and forwards the event to its own browsers over Server-Sent Events. Viewers on different servers stay in sync, with no Redis and no separate WebSocket server. Bursts of scores are grouped (200 ms) into a single ranking refresh per viewer. Quand un score est enregistré, le serveur publie un événement avec NOTIFY de PostgreSQL. Chaque serveur garde une connexion LISTEN et transmet l'événement à ses propres navigateurs par Server-Sent Events. Les spectateurs sur des serveurs différents restent synchronisés, sans Redis ni serveur WebSocket à part. Les rafales de scores sont regroupées (200 ms) en une seule mise à jour du classement par spectateur.عند حفظ نتيجة، ينشر الخادم حدثاً عبر NOTIFY في PostgreSQL. ويحتفظ كل خادم باتصال LISTEN وينقل الحدث إلى متصفحاته عبر Server-Sent Events. فيبقى المتابعون على خوادم مختلفة متزامنين، دون Redis ولا خادم WebSocket منفصل. وتُجمَّع دفعات النتائج (200 ms) في تحديث واحد للترتيب لكل متابع.

03Key decisionsChoix techniquesالخيارات التقنية

Fair ranking rulesDes règles de classement justesقواعد ترتيب عادلة

Each player's best score per period is computed in SQL (DISTINCT ON). Ties share a rank ("1224" style) and the earliest player comes first. Games can be high-score or lowest-wins (speedruns).Le meilleur score de chaque joueur sur la période est calculé en SQL (DISTINCT ON). Les ex aequo partagent un rang (style « 1224 ») et le premier arrivé passe devant. Un jeu peut être au meilleur score ou au plus petit temps (speedruns).تُحسب أفضل نتيجة لكل لاعب خلال الفترة بـ SQL (DISTINCT ON). المتعادلون يتقاسمون المرتبة (بأسلوب « 1224 ») ومن سبق يتقدّم. ويمكن أن تعتمد اللعبة على أعلى نتيجة أو على أقصر وقت (speedruns).

Secure API keysDes clés d'API sûresمفاتيح API آمنة

Each game gets its own key, shown once and stored only as a SHA-256 hash. Keys can be rotated from the admin panel, and comparisons are constant-time.Chaque jeu a sa propre clé, affichée une seule fois et stockée seulement sous forme de hash SHA-256. Les clés se renouvellent depuis l'admin, et les comparaisons sont en temps constant.لكل لعبة مفتاحها الخاص، يُعرض مرة واحدة ولا يُخزَّن إلا على شكل hash من نوع SHA-256. وتُجدَّد المفاتيح من لوحة الإدارة، وتتم المقارنات في زمن ثابت.

Built to fail wellPrévu pour les pannesمُصمَّم لتحمّل الأعطال

Score submission is rate-limited (token bucket, 429 with Retry-After). SSE streams send heartbeats and browsers reconnect on their own; the database listener reconnects with exponential backoff when Neon suspends an idle database.L'envoi de scores est limité (token bucket, 429 avec Retry-After). Les flux SSE envoient des signaux de vie et les navigateurs se reconnectent seuls ; l'écoute de la base se reconnecte avec un délai croissant quand Neon met une base inactive en veille.إرسال النتائج محدود المعدّل (token bucket، و 429 مع Retry-After). وترسل تدفقات SSE إشارات حياة وتعيد المتصفحات الاتصال تلقائياً؛ ويعيد الاستماع لقاعدة البيانات الاتصال بمهلة متزايدة عندما يضع Neon قاعدة غير نشطة في وضع السكون.

04ScreensÉcransالشاشات

05What I learnedCe que j'ai apprisما تعلّمته

  • Realtime without extra infrastructure: PostgreSQL LISTEN/NOTIFY and Server-Sent Events are often enough.Du temps réel sans infrastructure en plus : LISTEN/NOTIFY de PostgreSQL et les Server-Sent Events suffisent souvent.الوقت الحقيقي دون بنية إضافية: LISTEN/NOTIFY في PostgreSQL و Server-Sent Events تكفي غالباً.
  • Why some apps cannot run serverless: long-lived connections need a real Node.js server, and poolers do not support LISTEN.Pourquoi certaines applis ne peuvent pas tourner en serverless : les connexions longues demandent un vrai serveur Node.js, et les poolers ne gèrent pas LISTEN.لماذا لا يمكن لبعض التطبيقات العمل بنمط serverless: الاتصالات الطويلة تتطلب خادم Node.js حقيقياً، والـ poolers لا تدعم LISTEN.
  • Testing the rules that matter: ranking and ties, period boundaries, key hashing, session expiry and rate limiting are covered by unit tests.Tester les règles qui comptent : classement et ex aequo, limites des périodes, hash des clés, expiration de session et limitation de débit sont couverts par des tests unitaires.اختبار القواعد المهمة: الترتيب والتعادل، وحدود الفترات، وتشفير المفاتيح، وانتهاء الجلسة، وتحديد المعدّل، كلها مغطّاة باختبارات وحدات.

Next step: the rate limiter lives in memory, per server; behind a load balancer it should move to a shared store.Piste d'amélioration : la limitation de débit est en mémoire, par serveur ; derrière un répartiteur de charge, elle devrait passer dans un stockage partagé.تحسين ممكن: تحديد المعدّل محفوظ في الذاكرة لكل خادم؛ وخلف موزّع للحمل يجب نقله إلى تخزين مشترك.