Sniffari
Plateforme communautaire canine : matching, événements, services et questions-réponses, 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
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.
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.
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.
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.
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.
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.
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
- 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.
- 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.
- 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.
- 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.
- 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.
- 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