The context
Dorloter is a French platform dedicated to domestic animals, bringing together three services that are usually scattered: shelter adoption, lost and found reporting, and a directory of certified professional boarding facilities. The MVP targets cats and dogs, designed to extend to exotic pets without a rewrite.
It serves four kinds of actors, adopters, shelters, boarding facilities and administrators, through three distinct clients: a public showcase, a professional back-office and a mobile app. I designed and built it solo, in four months. Bringing three lines of business into one product is less a sum of features than a problem of boundaries, and three constraints follow from that.
Three lines of business, one product. Adoption, lost and found and boarding share neither actors, nor rules, nor pace of change, yet they share users, a database and an API. Each domain has to evolve without dragging the others along.
Matching that has to land right. Matching a lost animal against a found one crosses proximity, physical traits and date. Sorting in the application, after pulling the candidates out of the database, does not hold up.
A volunteer is not a global role. Rights inside a shelter depend on the team you belong to, not on the role carried by the authentication token. Conflating the two leads to a hierarchy of roles that always ends up buckling.
The reuniting chain
This is the function that justifies the data stack: everything else follows from it.
individual
Report
A lost or found animal is declared, with photos.
platform
Geolocation
The point is stored as geographic data, not as two numbers.
database
Proximity search
The database itself narrows opposite-type candidates to a perimeter.
platform
Scoring
Several weighted criteria give each candidate a single score.
platform
Introduction
Above the threshold, the match is offered to both reporters.
A report stays active: every new declaration is checked against those already stored, in both directions. An animal found today can match a loss reported last week.
Three consequences shaped the architecture. Position is stored as geometry rather than a pair of coordinates, which lets the database do the work. Matching is a continuous search, not a one-off query: data freshness matters as much as the accuracy of the computation. And the scoring scale sits in a pure function with no database access, which makes it verifiable without any infrastructure. That last boundary is the one that pays off most, and I come back to it below.
The technical choices
API
NestJS, modular monolith
Business modules in a single deployment, where each reaches the others only through their exported services. The boundaries of microservices without their operations, for a team of one.
Data access
Kysely, no ORM
Query typing is checked at compile time, but nothing stands between the code and the SQL. The schema is described in TypeScript and migrations remain versioned files, applied at startup.
Geospatial search
Delegated to the database
Proximity and distance are computed where the data lives, on an indexed geographic column, instead of pulling candidates out to filter them in memory.
Shelter authorisation
Permissions derived from the team
Fine-grained permissions derived from team roles. A volunteer invited in keeps the weakest global role: their rights come from membership, not from their token.
Two contract decisions round out those choices. API responses follow fixed envelopes, with stable error codes and cursor pagination: that contract has not moved since day one, so the three clients stay aligned without regeneration. And the platform carries no non-essential cookies, no third-party resources and no trackers, which exempts it from a consent banner and makes the published policy match what the code actually does.
The architecture that follows
Two shared packages keep the clients aligned without merging them: a design system common to both web apps, and an API access layer that carries tokens and their rotation. The showcase and the pro workspace have opposite audiences and opposite UX needs, so they stay two applications on two domains, and the API remains the only security boundary.
What the test suite covers
The suite is deliberately narrow. It does not chase a coverage percentage, it targets the places where a mistake is invisible on re-reading and breaks no journey: a scoring scale drifting by a point, a table forgotten in an erasure, a hash accepting what it should reject.
Completeness of GDPR erasure
The real risk is not that account deletion stops working, it is that a migration adds a table tied to a user and the erasure path forgets it. The test collects mandatory foreign keys to the accounts table, compares them against what the service actually purges, and fails on any gap. A newly tied table must be purged or declared as knowingly retained with its legal justification. The check is static: it runs with no database.
Matching scale
Since the scale is a pure function, each criterion is checked in isolation across its tiers, then their sum, then the case where everything diverges and the total must fall below the introduction threshold. No database, no fixtures, so no reason not to replay these cases on every run.
Password hashing
Reference vectors from the original implementation, Unicode normalisation of the password before hashing, encode-then-verify round trip, and rejection of a malformed hash without throwing. That last point matters: an uncaught error at verification turns corrupted data into an outage.
Mobile notification routing
Translating a notification into a destination inside the app, against hostile payloads: absent, carrying no usable identifier, carrying two competing identifiers, or holding unexpected types. A notification that opens the wrong screen is a bug no rendering test catches.
Twenty-one cases, fifty-two assertions, four files, under a second to run. That is little, and deliberately so: on an MVP built solo, a short suite that runs on every commit beats a broad one you end up disabling. The erasure test is the one I would write first on any product handling accounts.
The back-office that goes with it
A shelter is not run from a list of listings. The pro console covers the legal intake and outtake registry, adoption contracts, foster homes, medical follow-up, volunteers, events, stock and communication campaigns. It is the least visible part of the product and the largest.
Boarding facilities, meanwhile, go through a human check: company number and prefecture licence are verified before publication. It is the only point in the platform where a human deliberately blocks the flow, because a directory of certified professionals that does not verify certifications is worthless.
Where the product stands
The MVP scope is delivered: a 142-route API, three clients consuming the same contract, geospatial matching delegated to the database, and GDPR compliance written into the code rather than a page. Identified work remains, including browser notifications and an exhaustive API description. The design targets cats and dogs without closing the door on exotic pets: extending is a matter of data, not of a rewrite.


