
The full flow in 15 seconds: log in, open the map, search by name, and get a popup with contact details and a Google Maps directions link. The red pin is the user’s position; distances are computed from it.
Highlights#
- Built an Ionic/Angular mobile app that maps 167 authorized Italian car scrapyards and surfaces the closest one to the driver’s live GPS position, with one-tap Google Maps directions.
- Designed the NestJS/PostgreSQL API behind it: name and region filtering plus nearest-first ordering with a computed distance on every result.
- Converged three login paths — Google OAuth, Facebook OAuth, and email with a verification-token flow — on a single JWT session layer with access and refresh tokens.
- Containerized the stack with Docker Compose and wired GitLab CI to build and push deploy images to a private registry.
Architecture / How it works#
flowchart TD
subgraph app["Mobile app — Ionic 8 / Angular 19 / Capacitor"]
UI["Map + search UI"]
GEO["Capacitor Geolocation"]
LMAP["Leaflet map (OpenStreetMap tiles)"]
UI --- LMAP
GEO --> UI
end
UI -- "REST + JWT" --> API["NestJS API"]
API --> PG[("PostgreSQL")]
API -- "OAuth code exchange" --> IDP["Google / Facebook"]
API -- "verification email" --> SMTP["SMTP"]
SEED["Client dataset (locations.json)"] -- "seeder" --> PG
UI -- "directions deep link" --> GMAPS["Google Maps"]
Portfolio Case Study#
Problem. When a car in Italy reaches end of life, it legally has to go to an authorized demolition yard. Those yards exist as a flat list of business records: names, addresses, VAT numbers, phone numbers. A driver standing next to a dead car can’t do much with a list. The client had that data for 167 yards nationwide and wanted it usable: show me the yards near me, on a map, and get me there.
Approach. Two repositories: an Ionic 8 / Angular 19 app running on Capacitor, and a NestJS API over PostgreSQL. A seeder ingests the client’s Italian-schema dataset into Postgres, and the API exposes it with name/region filters. When the app sends the user’s coordinates, results come back nearest-first with a distance attached to each one.
Two decisions worth defending. The map is Leaflet on OpenStreetMap tiles, not the Google Maps SDK: OSM tiles are free, Google Maps needs a billing account, and that wasn’t worth it for a prototype. Directions still hand off to Google Maps through a plain deep link, which costs nothing and gives users the navigation app they already trust. Second, distance sorting happens in Node with geolib rather than in Postgres with PostGIS. Deliberate: at 167 rows an in-memory sort is simple and fast enough, and PostGIS would be overkill at this scale.
Auth got the most engineering depth. Three login paths (email with a verification-token flow, Google OAuth, Facebook OAuth) converge on one JWT session layer with access and refresh tokens. Passwords are bcrypt-hashed and sensitive user fields are encrypted with AES-256-CBC before they touch the database.
What was hard. Leaflet inside the Ionic webview was the biggest fight. The map initializes before the webview has settled on its real dimensions, so tiles render into a wrong-sized container; the fix was a deferred invalidateSize() after mount, plus bounds-fitting logic so the viewport frames all markers and the user pin without over-zooming. The reactive search was the other one: the query merges typed filters with a live GPS stream, and getting that wiring right in RxJS (debounced input, deduplicated emissions, form state that stays in sync without feedback loops) took real care to avoid stale positions and redundant API calls.
Outcome. A working prototype delivered at GoProject over roughly five weeks, covering the full flow from social login to map search to directions handoff. The delivery side was real even if the release never was: TypeScript end to end, Swagger docs on the API, a Docker Compose stack (API, Postgres, Redis, pgAdmin) with healthchecks, and a GitLab CI pipeline pushing images to the company registry from the staging branch. I authored about 90% of the commits across both repos, with two colleagues contributing smaller pieces.
Non-goals. It stayed an internal prototype: no App Store or Play Store packaging was ever done, and there is no admin UI for the yard data. Locations enter the system through the seeded client dataset, by design.
Screenshots#
Coverage view, the search sheet, a selected yard, and login.

Stack#
Ionic 8, Angular 19, Capacitor 7, Leaflet, RxJS, NestJS 11, TypeORM, PostgreSQL, Redis, Passport (Google/Facebook OAuth), JWT, Docker Compose, GitLab CI.
