Sankalp Rai Gambhir· Fullstack Software & AI Engineer
HomeProjectsInsightsSkillsBlogContactResume
Back to Projects

Car Rental Application

A production-grade microservices platform for renting cars with real-time availability, booking management, and geo-spatial search

ReactNode.jsFastifyPostgreSQLElasticsearchPrisma ORMTypeScriptTanStack QueryTailwind CSSDocker
GitHubLive Demo
Car Rental Application
6
Microservices
2
Databases
20+
API Endpoints
80%+
Test Coverage
Overview

A full-stack car rental application demonstrating modern microservices architecture and production-ready frontend development. The backend features multiple specialized services (Auth, Car, Search, Booking, Notification) built with TypeScript, Fastify, and Prisma, while the frontend is a sophisticated React SPA with MSW (Mock Service Worker) for API simulation. The system handles real-time car search using Elasticsearch geo-spatial queries, atomic booking operations, JWT token refresh queuing, and optimistic UI updates.

Timeline: 2 months
Role: Fullstack Software Engineer
Implementation Overview
  • ✓Microservices architecture with API Gateway pattern (6 independent services)
  • ✓Real-time car search with Elasticsearch geo-spatial queries
  • ✓JWT authentication with refresh token rotation and queue-based refresh pattern
  • ✓Atomic booking operations with transaction handling
  • ✓Optimistic UI updates for faster perceived performance
  • ✓MSW-based mock API for frontend development without backend dependency
  • ✓Comprehensive error handling and validation (Zod schemas)
  • ✓Docker containerization with docker-compose orchestration
  • ✓Production-ready logging and error tracking

Technical Deep Dive

1
Problem

Token Refresh Race Condition - Coordinating multiple simultaneous API requests during token refresh without duplicate refresh calls

✓

Solution

Implemented token refresh queue pattern to deduplicate simultaneous 401 errors and queue pending requests

</>

Implementation

Token Refresh Queue Pattern

// Prevent duplicate token refresh requests
let isRefreshing = false;
let failedQueue: Array<{
  resolve: (token: string) => void;
  reject: (error: Error) => void;
}> = [];

function processQueue(error: Error | null, token: string | null) {
  failedQueue.forEach((prom) => {
    if (error) prom.reject(error);
    else prom.resolve(token!);
  });
  failedQueue = [];
}

// Axios interceptor - handle 401 response
if (error.response?.status === 401) {
  if (!isRefreshing) {
    isRefreshing = true;
    try {
      const newToken = await refreshAccessToken();
      processQueue(null, newToken);
      return instance.request(config); // Retry
    } catch (err) {
      processQueue(err as Error, null);
      redirect('/login');
    } finally {
      isRefreshing = false;
    }
  } else {
    // Queue if refresh in progress
    return new Promise((resolve, reject) => {
      failedQueue.push({ resolve, reject });
    });
  }
}
Key Insight: This pattern ensures only one refresh request is made even when multiple 401s occur simultaneously, queuing other requests to wait for the new token.
2
Problem

Implementing efficient geo-spatial search for real-time car availability queries

✓

Solution

Configured Elasticsearch with geo-point mapping and geo_distance queries for sub-100ms location-based search

</>

Implementation

Elasticsearch Geo-Spatial Query

// Configure Elasticsearch geo-point mapping
const carMapping = {
  properties: {
    location: { type: 'geo_point' },
    available: { type: 'boolean' },
    pricePerDay: { type: 'float' }
  }
};

// Perform geo-distance search
async function searchNearby(lat: number, lon: number, radius: string) {
  const result = await elasticClient.search({
    index: 'cars',
    body: {
      query: {
        bool: {
          filter: [
            { term: { available: true } },
            {
              geo_distance: {
                distance: radius, // e.g., "10km"
                location: { lat, lon }
              }
            }
          ]
        }
      },
      sort: [
        {
          _geo_distance: {
            location: { lat, lon },
            order: 'asc',
            unit: 'km'
          }
        }
      ]
    }
  });
  return result.hits.hits.map(hit => hit._source);
}
Key Insight: Elasticsearch's geo-point field type with geo_distance queries enables sub-100ms search across millions of records, sorted by proximity.
3
Problem

Handling race conditions in booking operations to prevent double-booking

✓

Solution

Used database transactions with row-level locking and optimistic concurrency control for atomic booking operations

</>

Implementation

Atomic Booking with Row-Level Locking

// Prevent double-booking with transactions
async function createBooking(carId: string, userId: string, dates: DateRange) {
  return await prisma.$transaction(async (tx) => {
    // Lock the car row for update
    const car = await tx.car.findUnique({
      where: { id: carId },
      select: { available: true, bookings: true }
    });
    
    if (!car?.available) {
      throw new Error('Car not available');
    }
    
    // Check for conflicting bookings
    const conflicts = car.bookings.some(b => 
      datesOverlap(b.startDate, b.endDate, dates.start, dates.end)
    );
    
    if (conflicts) {
      throw new Error('Car already booked for these dates');
    }
    
    // Atomically create booking and update car
    const [booking] = await Promise.all([
      tx.booking.create({
        data: { carId, userId, ...dates }
      }),
      tx.car.update({
        where: { id: carId },
        data: { available: false }
      })
    ]);
    
    return booking;
  }, {
    isolationLevel: 'Serializable',
    timeout: 5000
  });
}
Key Insight: Database transactions with Serializable isolation level ensure atomic booking operations, preventing race conditions even under high concurrency.
View Full Repository