AH
Ali Hasan
← Back to all projects
distributed • Case Study

NowHere: Distributed Geospatial & Real-Time Microservices

High-Concurrency Location-Based Discovery Engine Built with NestJS, NATS JetStream & React Native

NowHere: Distributed Geospatial & Real-Time Microservices
1,000+
Concurrency
Simultaneous active WebSocket client connections
5MB → 1KB
Upload Pipeline
Direct-to-S3 bypass eliminates gateway bandwidth bottlenecks
< 100ms
Geospatial Latency
Real-time vectorized Haversine proximity broadcast
5 Services
Microservices
NATS Request-Reply RPC + Durable JetStream Events
Technologies & Architecture Stack:
NestJS 11 NATS JetStream MongoDB 2dsphere MySQL & TypeORM Redis Cache React Native Expo 53 Docker & Kubernetes Socket.IO

Executive Overview

NowHere is an enterprise-grade, distributed geolocation discovery platform engineered to answer a core real-time question: “What is happening right around me right now?”

Unlike traditional social platforms that push algorithmic feeds stored in centralized relational databases, NowHere enables hyper-local, ephemeral discovery. Users capture and view nearby “Snaps” categorized as Lost & Found, Hidden Gems, Local Promotions, or Live Events.

Key Architectural Requirements:

  1. High Concurrency & Low Latency: Real-time geofenced notifications broadcast to active clients within 100km radii with sub-100ms delivery.
  2. Zero-Bottleneck Media Ingestion: Camera feeds producing 5MB raw images must not choke API gateway bandwidth or saturate Node.js event loops.
  3. Decoupled Distributed Services: Discrete microservices communicating via microsecond-level RPCs with guaranteed at-least-once asynchronous event delivery.
  4. Multi-Cloud Vendor Portability: Clean separation of secrets management and blob storage across AWS, GCP, and local containerized environments via the Strategy Pattern.

High-Level System Context & Topology

NowHere segregates client ingress, API gateway routing, internal event messaging, and dedicated polyglot database persistence into cleanly isolated boundaries:

System Architecture & Topology Figure 1: High-level architectural topology showing client ingress, API gateway routing, NATS message bus, domain microservices cluster, and polyglot database persistence.


The 5 Core Microservices

The backend is architected as an Nx Monorepo containing five decoupled applications:

Microservices Class & Interaction Diagram Figure 2: Microservices class and interaction architecture displaying controllers, data layers, and NATS subject patterns.

1. API Gateway (apps/gateway)

Acts as the single point of entry on port 3005, shielding internal microservices from the public internet.

  • Centralized Authentication Guard (GatewayAuthGuard): Validates incoming Bearer JWT tokens, injects user payloads into request scopes, and prevents unauthorized requests from touching the internal NATS bus.
  • Envelope Standardization (DataResponseInterceptor): Wraps all successful responses in a unified { success: true, data: T } envelope.
  • Error Normalization (HttpExceptionFilter): Converts exceptions into RFC 9457 Problem Details schemas with explicit type, title, status, detail, and instance properties.

2. Authentication Service (apps/authentication)

Owns credential storage and token lifecycle management.

  • Storage: Dedicated MySQL database using TypeORM.
  • Security: Argon2 hashing for credentials, stateless RS256 JWT access tokens, and rolling refresh tokens.
  • NATS Patterns: Listens to auth.login, auth.signup, auth.refresh, auth.validate_token, and auth.me.

3. Users Service (apps/users)

Manages user profiles, social relationships, and discovery radius configurations.

  • Storage: Independent MySQL schema (users, settings, snaps_seen).
  • NATS Patterns: Responds to users.get_by_id, users.update_profile, users.set_photo, and users.get_settings.
  • Cross-Service Communication: Queries the storage microservice over NATS request-reply to retrieve temporary signed download URLs for user avatars.

4. Snaps Service (apps/snaps)

The core geospatial discovery engine.

  • Storage: MongoDB utilizing a native 2dsphere spatial index on location.coordinates ([longitude, latitude]).
  • Real-Time Gateway: An integrated Socket.IO server tracking live client coordinates in memory for real-time localized event dispatching.
  • NATS Patterns: Handles snaps.create, snaps.get_nearby, and snaps.get_by_id.

5. Storage Service (apps/storage)

An isolated microservice responsible for media management and cloud abstraction.

  • Cloud Strategy Pattern: Supports AWS S3, Cloudflare R2, MinIO, and Google Cloud Storage (GCS).
  • Presigned URL Pipeline: Generates temporary cryptographic PUT/GET URLs so that binary media bypasses internal server memory completely.
  • Redis Caching: Download URLs are cached in Redis with strict TTLs (CACHE_TTL = 86340s) to avoid redundant signature computations.

Detailed Data Flows & Sequence Workflows

1. Direct-to-S3 Presigned Media Ingestion Pipeline

Handling 5MB camera image uploads synchronously through an API gateway saturates Node.js network sockets and increases memory footprint. NowHere solves this by decoupling upload authorization from binary transfer:

Direct-to-S3 Presigned Sequence Diagram Figure 3: Sequence workflow demonstrating direct-to-S3 presigned upload bypass and lightweight metadata persistence.


2. Real-Time Proximity Broadcast & Vectorized Haversine Engine

When a snap is created, it must be pushed immediately to active users nearby without polling:

Real-Time Haversine Proximity Sequence Figure 4: Sequence diagram detailing spatial proximity filtering using the Haversine distance formula.

The Haversine Mathematical Implementation (SnapsGateway):

getDistanceInMeters(lat1: number, lon1: number, lat2: number, lon2: number): number {
  const toRadians = (deg: number) => deg * (Math.PI / 180);
  const R = 6371e3; // Earth radius in meters

  const dLat = toRadians(lat2 - lat1);
  const dLon = toRadians(lon2 - lon1);

  const a =
    Math.sin(dLat / 2) ** 2 +
    Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2)) * Math.sin(dLon / 2) ** 2;

  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  return R * c; // Distance in meters
}

Inter-Service Communication: NATS Request-Reply vs. JetStream

NowHere divides communication into two distinct message delivery guarantees:

NATS Request-Reply vs JetStream Figure 5: Comparison between synchronous NATS request-reply RPCs and asynchronous durable JetStream event streams.

1. Synchronous NATS Request-Reply

  • Used when the API Gateway requires immediate data to answer an HTTP request (e.g., authenticating credentials or retrieving user settings).
  • Latency is under 1ms on local container bridges, significantly faster than HTTP/1.1 REST calls between services.

2. Asynchronous NATS JetStream (At-Least-Once Delivery)

  • Used for decoupled side-effects where the client should not wait (e.g., notifying subscribers, processing analytics, updating user streaks).
  • If a downstream worker crashes, JetStream’s durable consumer maintains message offsets and retries upon worker restart.

Multi-Cloud Portability: Strategy Pattern

To prevent vendor lock-in across commercial cloud ecosystems, secrets retrieval and blob storage are abstracted behind TypeScript strategy interfaces.

Cloud Portability via Strategy Pattern Figure 6: Strategy Pattern class structure enabling multi-cloud portability across AWS, GCP, and local environments.

By specifying SECRETS_PROVIDER=gcp or STORAGE_PROVIDER=aws, the NestJS dependency injection container dynamically binds the corresponding concrete implementation at runtime with zero changes to business logic.


Frontend Integration & State Architecture (React Native / Expo 53)

The mobile client is built on React Native (Expo 53) utilizing a multi-layered offline-first data flow:

Frontend Architecture & Data Flow Figure 7: Frontend architecture diagram showing UI screens, TanStack React Query, Zustand, and MMKV C++ storage.

Technical Highlights of Frontend Implementation:

  • Fast Storage via MMKV: Uses the C++ MMKV engine instead of AsyncStorage, executing key-value transactions 30x faster and eliminating frame drops when caching dense geospatial arrays.
  • Resilient Auth Hydration: JWT access tokens and refresh tokens are stored in hardware-encrypted Expo SecureStore. On cold boot, useAuthSession seamlessly validates the session against /auth/me without flickering unauthenticated screens.
  • Offline Coordinate Smoothing: Real-time user position changes are debounced before transmission over WebSocket, conserving battery life while maintaining accurate proximity calculation.

Architectural Decision Records (ADRs)

DecisionChosen SolutionAlternatives EvaluatedEngineering Rationale
Inter-Service BrokerNATS Request-Reply + JetStreamRabbitMQ, gRPC, HTTP RESTMicrosecond RPC latency, native pub/sub, zero connection pooling overhead, and built-in durable message replay.
Geospatial EngineMongoDB 2dsphere IndexingPostgreSQL + PostGISFlexible JSON document schema for dynamic tagging, combined with native millisecond $nearSphere queries.
Media PipelineDirect Presigned S3 BypassMultipart Stream through GatewayEliminates API gateway memory leaks, reduces server bandwidth costs to near-zero, and leverages S3 multi-part acceleration.
Error Handling ContractRFC 9457 Problem DetailsAd-hoc error stringsUniform machine-readable error format (type, status, detail, errors[]) shared across all services and mobile clients.
Cache LayerRedis (Distributed Cache)Node.js In-Memory MapShares signed media URL cache across scaled service replicas, preventing redundant signature generations.

Local Development & Container Verification

# 1. Clone the monorepo
git clone https://github.com/AliSaleemHasan/NowHere.git
cd NowHere/backend

# 2. Spin up full microservices stack via Docker Compose
# (NATS, MySQL Auth, MySQL Users, MongoDB Snaps, MinIO S3, Redis Cache)
docker compose -f docker-compose.dev.yml up -d

# 3. Verify health checks across all services:
# - API Gateway:        http://localhost:3005/health
# - Snaps Service:      http://localhost:3002/health
# - Users Service:      http://localhost:3001/health
# - Auth Service:       http://localhost:3000/health
# - Storage Service:    http://localhost:3004/health

# 4. Execute unit & integration test suites via Nx
pnpm nx run-many --target=test --all