Aller au contenu
HL
DisponibleEN
Étude de cas / weben ligne

Sniffari

Plateforme communautaire canine : matching, événements, services et questions-réponses, en ligne sur sniffaridogs.com

Page d'accueil de Sniffari avec une image hero de quatre chiens sur une forme verte, à côté du titre et d'une liste de fonctionnalités
Le hero d'accueil centré sur l'image que j'ai reconstruit, en ligne sur sniffaridogs.com.

En bref

Plateforme
Web (ordinateur et mobile), plus une API REST publique
Calendrier
2026 (livraison en production le 14 juin ; 7 cycles de modifications jusqu'au 29 juin)
Rôle
Ingénieur full-stack (seul) sur un prototype front-end fourni par le client
Périmètre backend
17 modules d'API, 19 modèles Prisma, 6 migrations
Rebranchement du frontend
74 fichiers modifiés, 16 fichiers ajoutés, environ 4 900 lignes ajoutées
Tests
241 tests d'API backend (Jest + Supertest), 16 tests unitaires Vitest, 3 smoke tests end-to-end Playwright

Vue d'ensemble

Sniffari est une plateforme web communautaire pour les propriétaires et les amoureux des chiens : profils de chiens, matching de rencontres entre chiens façon swipe, événements, échange gratuit de garde et de promenade, messagerie privée, questions-réponses communautaires, avis, calendrier de soins et blog. Le client m'a remis un frontend Next.js qui ne fonctionnait qu'à partir d'un fichier statique de données fictives. J'ai construit l'intégralité du backend, j'y ai connecté chaque écran et j'ai mené le produit jusqu'en production sur sniffaridogs.com.

Le défi

Le livrable ressemblait à une application mais n'effectuait aucun appel réseau : les données venaient d'un unique fichier statique, l'inscription et la connexion ne faisaient rien, le matching ne pouvait pas fonctionner car les chiens étaient créés avec un nom, une race et un sexe vides, et le site n'avait jamais été déployé. Il comportait aussi des textes provisoires, des pages légales en lorem ipsum et un mauvais nom de domaine. Le client avait besoin d'un produit fonctionnel sur son propre VPS avec de vrais comptes, et chaque écran existant devait conserver son design tout en étant rebranché sur des données réelles. Les cycles suivants ont ajouté des exigences touchant les deux côtés : inscriptions limitées aux pays de lancement, vérification de l'e-mail et une vue d'administration.

Mon rôle

J'ai travaillé seul sur toute la stack, en commençant par l'API Node.js/Express/TypeScript, que j'ai conçue et écrite de zéro (17 modules fonctionnels, un schéma Prisma de 19 modèles ayant évolué au fil de 6 migrations, une documentation OpenAPI). Côté frontend, j'ai écrit le client d'API et la couche de services, rebranché dessus les écrans Next.js existants (74 fichiers existants modifiés, 16 nouveaux fichiers), et repensé le parcours d'inscription/connexion ainsi que le hero de la page d'accueil. J'ai aussi écrit les suites de tests (API backend, tests unitaires et end-to-end du frontend) et un workflow GitHub Actions, réalisé un audit de code suivi d'une passe de durcissement, puis déployé et exploité la stack sur le VPS du client avec nginx, PM2 et Let's Encrypt. Le travail a été livré en une première livraison, suivie de sept cycles de modifications tracés.

Architecture

  1. Clients

    • Application web Sniffari

      Next.js 15 (App Router), React 19, Redux Toolkit, Tailwind CSS 4

      L'interface existante du client, rebranchée d'un fichier statique de données fictives vers les données de l'API en direct, avec un hook de garde d'authentification sur les pages privées.

    • Client d'API et couche de services

      lib/api.js, lib/services.js

      Un unique wrapper fetch qui ajoute le JWT et gère les 401 de façon centralisée, plus une fonction de service pour chaque fonctionnalité du backend.

  2. Edge/Diffusion

    • Reverse proxy et TLS

      nginx, Let's Encrypt

      Sert le site sur le domaine principal et l'API sur son propre sous-domaine, avec redirection de HTTP vers HTTPS et certificats renouvelés automatiquement.

  3. Services

    • API REST

      Node.js, Express, TypeScript, Zod

      17 modules de routes/handlers (auth, users, dogs, playdate, events, messages, exchange, appointments, community, reviews, blog, notifications, contact, newsletter, reports, upload, admin) avec validation des requêtes par Zod.

    • Authentification

      JWT, bcryptjs, google-auth-library

      Connexion par e-mail/mot de passe et par Google, inscription réservée aux 18 ans et plus, codes à 6 chiffres de vérification de l'e-mail et de réinitialisation du mot de passe avec expiration et nombre de tentatives plafonné (plus un délai avant renvoi pour la vérification), et routes d'administration restreintes par rôle.

    • Restriction régionale des inscriptions

      fast-geoip (offline), declared-city check

      Les inscriptions sont limitées à une liste de pays de lancement définie par variable d'environnement, à partir de la ville déclarée et d'une géolocalisation IP hors ligne ; la connexion n'est jamais bloquée.

    • Contrôle du trafic et endpoints d'exploitation

      express-rate-limit, helmet, CORS allowlist

      Limites strictes sur les endpoints d'identifiants et de codes, limites modérées sur les endpoints qui envoient des e-mails, un throttle global, et les endpoints /health, /ready (avec vérification de la base de données) et /geo/availability.

  4. Média

    • Envoi d'images

      Multer, local disk, express.static

      Les photos de profil et de chiens sont stockées sur le disque du serveur sous des noms de fichiers UUID et servies par l'API avec une cross-origin resource policy permettant au frontend de les intégrer.

  5. Données

    • Base de données relationnelle

      PostgreSQL, Prisma ORM

      19 modèles (utilisateurs, chiens, swipes, événements et participants, messages, annonces d'échange, demandes de service, rendez-vous, avis, questions-réponses, notifications, etc.) gérés au fil de 6 migrations.

  6. Services tiers

    • E-mail, monitoring et analytics

      Nodemailer (SMTP), Sentry, PostHog

      Intégrations activées par variables d'environnement, inactives lorsqu'elles ne sont pas configurées : e-mails transactionnels pour la vérification, les réinitialisations et les avis de contact/signalement, remontée d'erreurs sur les deux applications, et analytics produit côté frontend.

  7. Infrastructure

    • Hébergement et gestion des processus

      Ubuntu VPS, PM2

      L'API et le serveur Next.js tournent sous un utilisateur non root avec PM2, redémarrent automatiquement après un reboot et ne sont accessibles de l'extérieur qu'à travers nginx.

    • Workflow de CI

      GitHub Actions

      Définit un contrôle npm audit pour les deux applications, le typecheck du backend et Jest contre un service Postgres 16, ainsi que le lint, Vitest, le build et Playwright pour le frontend.

Décisions clés

  1. 01

    Construire le backend autour des écrans existants

    Plutôt que de demander une réécriture du frontend, j'ai modélisé l'API et le schéma Prisma d'après ce que les écrans livrés affichaient déjà, puis j'ai remplacé les données fictives écran par écran via un unique client d'API. Le design est resté tel que le client l'avait validé ; en contrepartie, certaines structures du backend ont dû s'adapter à des choix d'interface que je n'aurais pas faits en partant de zéro.

  2. 02

    Regrouper les écritures en plusieurs étapes dans une seule transaction

    Accepter une demande de service met à jour son statut, crée la réservation dans les calendriers du demandeur et du prestataire, et envoie une notification ; envoyer un message écrit aussi une notification. Les deux s'exécutent dans des transactions Prisma : un échec en cours de route ne peut donc pas laisser une demande acceptée sans ses réservations, ni un message sans sa notification. Cela ajoute un peu de code en échange de données cohérentes.

  3. 03

    Matcher sur des paires de chiens exactes

    Les matchs mutuels sont calculés à partir de paires de likes exactes (mon chien, l'autre chien), et non d'une liste à plat de chiens likés. Les propriétaires de plusieurs chiens ne voient un match que si cette paire précise s'est likée mutuellement. La requête fait plus de travail à chaque appel, mais ne signale plus de faux matchs entre paires croisées.

  4. 04

    Une restriction régionale qui ne bloque jamais les vrais utilisateurs

    Les inscriptions sont limitées aux pays de lancement à partir de la ville déclarée et d'une géolocalisation IP hors ligne ; les utilisateurs existants peuvent toujours se connecter, et la liste des pays est une variable d'environnement.

  5. 05

    Des intégrations optionnelles qui se dégradent en no-op

    SMTP, Sentry, PostHog et la connexion Google s'activent tous par variables d'environnement. Sans clés, ils ne font rien et les données des formulaires sont tout de même enregistrées, si bien que personne ne se retrouve bloqué. Cela a permis aux environnements local, de test et de production initiale de fonctionner avant que les comptes du client soient prêts, au prix d'une vérification manuelle de chaque intégration une fois activée.

  6. 06

    Corriger le problème d'échelle à la source

    L'interface héritée dimensionnait la typographie et les espacements en unités de viewport, si bien que tout paraissait zoomé sur les grands écrans. J'ai converti en pixels fixes 2 788 valeurs de police et d'espacement dans 70 fichiers, sans toucher aux vraies règles de mise en page responsive, ce qui a réglé le problème sur tout le site plutôt que page par page.

Résultats

  • En production sur sniffaridogs.com, avec l'API sur son propre sous-domaine HTTPS et une documentation OpenAPI/Swagger publique.
  • Un frontend qui ne faisait aucun appel réseau est devenu un produit fonctionnel : vrais comptes, connexion Google, vérification de l'e-mail, matching de rencontres, inscription aux événements avec limite de places, messagerie avec compteurs de non-lus, échange de services avec réservations pour les deux parties, questions-réponses, avis et une page d'administration en lecture seule.
  • Des suites de tests automatisés (API, unitaires et end-to-end) s'exécutent dans un workflow GitHub Actions avec un contrôle npm audit.
  • Passe de durcissement après un audit de code structuré : limitation de débit, index de base de données sur les chemins critiques, gestionnaires de plantage au niveau du processus avec Sentry, et mises à jour des dépendances sur les deux applications.
  • Sept cycles de modifications tracés livrés après la livraison initiale, dont une refonte en deux panneaux de l'inscription/connexion, un hero d'accueil centré sur l'image, des avis de disponibilité régionale et la vérification de l'e-mail.

Stack

Frontend
Next.js 15React 19Tailwind CSS 4Redux ToolkitMUISwiper
Backend
Node.jsExpressTypeScriptZodJWTbcryptjsGoogle Sign-InMulterNodemailerexpress-rate-limithelmetfast-geoip
Données
PostgreSQLPrisma
Observabilité et analytics
SentryPostHogSwagger/OpenAPI
Tests et CI
JestSupertestVitestPlaywrightGitHub Actions
Infrastructure
Ubuntu VPSnginxPM2Let's Encrypt

Sniffari · captures d'écran

Fenêtre modale d'inscription en deux panneaux, avec à gauche un panneau vert aux couleurs de la marque et à droite un formulaire de compte vide avec les champs âge, ville, e-mail et mot de passe et un bouton Continue with Google
1 / 6La fenêtre d'inscription repensée : champ d'âge, sélecteur de ville avec recherche et indication de disponibilité régionale, et connexion Google.