Skip to content
HL
Available
Case study / weblive

Sniffari

Dog-community platform: matching, events, services and Q&A, live at sniffaridogs.com

Sniffari home page with a hero image of four dogs on a green shape beside the headline and a list of features
The picture-first home hero I rebuilt, live at sniffaridogs.com.

At a glance

Platform
Web (desktop and mobile), plus a public REST API
Timeline
2026 (production delivery 14 June; 7 change rounds through 29 June)
Role
Solo full-stack engineer on a client-supplied front-end prototype
Backend scope
17 API modules, 19 Prisma models, 6 migrations
Front-end rewiring
74 files changed, 16 files added, about 4,900 lines added
Tests
241 backend API tests (Jest + Supertest), 16 Vitest unit tests, 3 Playwright end-to-end smoke tests

Overview

Sniffari is a community web platform for dog owners and dog lovers: dog profiles, swipe-style playdate matching, events, a no-fee dog-sitting and walking exchange, private messaging, community Q&A, reviews, a care calendar and a blog. The client handed over a Next.js front end that ran only on a static mock-data file. I built the entire backend, connected every screen to it, and took the product to production at sniffaridogs.com.

The challenge

The hand-off looked like an app but made no network calls at all: data came from a single static file, sign-up and login did nothing, matching could not work because dogs were created with blank name, breed and gender, and the site had never been deployed. It also shipped with placeholder copy, lorem-ipsum legal pages and a wrong domain. The client needed a working product on their own VPS with real accounts, and every existing screen had to keep its design while being rewired to live data. Later rounds added requirements that touched both sides: sign-ups limited to launch countries, email verification, and an admin view.

My role

I worked alone across the whole stack, starting with the Node.js/Express/TypeScript API, which I designed and wrote from scratch (17 feature modules, a Prisma schema of 19 models evolved through 6 migrations, OpenAPI docs). On the front end, I wrote the API client and service layer, rewired the existing Next.js screens to it (74 existing files changed, 16 new files), and redesigned the sign-up/login flow and the home hero. I also wrote the test suites (backend API, front-end unit and end-to-end) and a GitHub Actions workflow, ran a code audit followed by a hardening pass, and deployed and operated the stack on the client's VPS with nginx, PM2 and Let's Encrypt. The work shipped as an initial delivery followed by seven tracked change rounds.

Architecture

  1. Clients

    • Sniffari web app

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

      The client's existing UI, rewired from a static mock file to live API data, with an auth-guard hook on private pages.

    • API client and service layer

      lib/api.js, lib/services.js

      One fetch wrapper that attaches the JWT and handles 401s centrally, plus a service function for every backend feature.

  2. Edge/Delivery

    • Reverse proxy and TLS

      nginx, Let's Encrypt

      Serves the site on the main domain and the API on its own subdomain, with HTTP redirected to HTTPS and auto-renewing certificates.

  3. Services

    • REST API

      Node.js, Express, TypeScript, Zod

      17 route/handler modules (auth, users, dogs, playdate, events, messages, exchange, appointments, community, reviews, blog, notifications, contact, newsletter, reports, upload, admin) with Zod request validation.

    • Authentication

      JWT, bcryptjs, google-auth-library

      Email/password and Google sign-in, 18+ registration, 6-digit email-verification and password-reset codes with expiry and attempt caps (plus a resend cooldown on verification), and role-gated admin routes.

    • Regional sign-up gating

      fast-geoip (offline), declared-city check

      Sign-ups are limited to an environment-configured list of launch countries using the declared city plus an offline IP lookup; sign-in is never blocked.

    • Traffic controls and ops endpoints

      express-rate-limit, helmet, CORS allowlist

      Strict limits on credential and code endpoints, moderate limits on mail-sending endpoints, a global throttle, and /health, /ready (database-checked) and /geo/availability endpoints.

  4. Media

    • Image uploads

      Multer, local disk, express.static

      Profile and dog photos are stored as UUID-named files on the server's disk and served by the API with a cross-origin resource policy so the front end can embed them.

  5. Data

    • Relational database

      PostgreSQL, Prisma ORM

      19 models (users, dogs, swipes, events and participants, messages, exchange posts, service requests, appointments, reviews, Q&A, notifications and more) managed through 6 migrations.

  6. Third-party

    • Email, monitoring and analytics

      Nodemailer (SMTP), Sentry, PostHog

      Environment-gated integrations that do nothing when unconfigured: transactional email for verification, resets and contact/report notices, error reporting on both apps, and front-end product analytics.

  7. Infrastructure

    • Hosting and process management

      Ubuntu VPS, PM2

      The API and the Next.js server run as a non-root user under PM2, restart automatically on reboot, and are reachable from outside only through nginx.

    • CI workflow

      GitHub Actions

      Defines an npm audit gate for both apps, backend typecheck and Jest against a Postgres 16 service, and a front-end lint, Vitest, build and Playwright run.

Key decisions

  1. 01

    Build the backend around the existing screens

    Rather than asking for a front-end rewrite, I modeled the API and Prisma schema on what the delivered screens already showed, then replaced the mock data screen by screen through a single API client. The design stayed as the client approved it; the cost was fitting some backend shapes to UI choices I would not have made from scratch.

  2. 02

    Keep multi-step writes in one transaction

    Accepting a service request updates its status, creates the booking on both the requester's and the provider's calendars, and sends a notification; sending a message also writes a notification. Both run inside Prisma transactions, so a failure part-way cannot leave a request accepted without its bookings, or a message without its notification. It adds some code in exchange for consistent data.

  3. 03

    Match on exact dog pairs

    Mutual matches are computed from exact (my dog, other dog) like pairs, not from a flat list of liked dogs. Owners with several dogs only see a match when that specific pair liked each other. The query does more work per request but no longer reports false cross-paired matches.

  4. 04

    Region gating that never locks out real users

    Sign-ups are limited to launch countries using the declared city plus an offline IP lookup; existing users can always sign in, and the country list is an environment variable.

  5. 05

    Optional integrations degrade to no-ops

    SMTP, Sentry, PostHog and Google sign-in are all switched on by environment variables. With no keys set they do nothing and form data is still saved, so nobody gets locked out. That kept local, test and early production environments working before the client's accounts were ready, at the cost of having to verify each integration by hand once it was switched on.

  6. 06

    Fix the scaling issue at its source

    The inherited UI sized type and spacing in viewport units, so everything looked zoomed-in on large monitors. I converted 2,788 font and spacing values across 70 files to fixed pixels and left the real responsive layout rules alone, which fixed the problem across the whole site instead of page by page.

Results

  • Live in production at sniffaridogs.com, with the API on its own HTTPS subdomain and public OpenAPI/Swagger documentation.
  • A front end that made no network calls became a working product: real accounts, Google sign-in, email verification, playdate matching, event joins with capacity limits, messaging with unread counts, a service exchange with bookings for both parties, Q&A, reviews and a read-only admin page.
  • Automated API, unit and end-to-end test suites run in a GitHub Actions workflow with an npm audit gate.
  • Hardening pass after a structured code audit: rate limiting, database indexes on hot paths, process-level crash handlers with Sentry, and dependency upgrades on both apps.
  • Seven tracked change rounds shipped after the initial delivery, including a two-panel sign-up/login redesign, a picture-first home hero, regional availability notices and email verification.

Stack

Frontend
Next.js 15React 19Tailwind CSS 4Redux ToolkitMUISwiper
Backend
Node.jsExpressTypeScriptZodJWTbcryptjsGoogle Sign-InMulterNodemailerexpress-rate-limithelmetfast-geoip
Data
PostgreSQLPrisma
Observability and analytics
SentryPostHogSwagger/OpenAPI
Testing and CI
JestSupertestVitestPlaywrightGitHub Actions
Infrastructure
Ubuntu VPSnginxPM2Let's Encrypt

Sniffari · screenshots

Two-panel sign-up modal with a green branded panel on the left and an empty account form with age, city, email and password fields and a Continue with Google button on the right
1 / 6The redesigned sign-up modal: age field, searchable city picker with a regional availability hint, and Google sign-in.