User Tools

Site Tools


flight_booking_bff

Flight Booking BFF — Microservices Architecture & Implementation Guide

Overview

This document describes the Backend-for-Frontend (BFF) microservices architecture for the Flight Booking. The BFF exposes secure APIs to the React TypeScript frontend and integrates with:

  • Token-based authentication
  • Third-party Flight Search APIs
  • Redis Cache (5-minute TTL)
  • Booking Service (MySQL)
  • Payment Gateway

Architecture Summary

Key Services / Core Components

ServiceResponsibilityLocal Port
API Gateway / BFF (Node.js) Single entry point for UI4000
Auth Service Issues & validates tokens4001
Flight Service Calls supplier API + Redis cache4002
Booking Service (MySQL) Stores booking data4003
Payment Service Payment initiation + webhooks4004

Observability Stack

  • Pino
  • Pino-Http
  • OpenTelemetry
  • Cloud Logging (ELK / CloudWatch / Loki)

Token Flow Diagram

[Frontend] → login → [Auth Service] \
           ← JWT Token \
[Frontend] → API calls with Bearer Token → [BFF]

Tokens are validated per request.

Flight Caching Strategy (Redis — 5 mins TTL)

  • Supplier API calls are expensive and time-sensitive
  • Data is cached in Redis for 300 seconds
  • UI retrieves additional details from cache (“Show More”)

Redis Key Naming Convention

flights:{origin}:{destination}:{date}:{userID}:{searchHash}

Examples:

flights:BOM:DXB:2025-03-21:user123:8f3c0e1a76

Booking Workflow — Sequence

Search Flights → Cache Results
Select Flight → Create booking
Initiate Payment → Payment Gateway
Webhook Received → Confirm booking
Persist in MySQL → Send Confirmation

Node.js Folder Structure (Clean Architecture)

LayerPurposeExamplesShould define errors?
Domain Business rulesEntities, aggregatesBusiness errors
Domain/Application Use-cases / orchestrationServices, DTOsApp errors
Infrastructure Technical adaptersDB, Redis, HTTPAdapter-only errors
Interface/API ControllersREST/GraphQLno core errors
src/
 ├ api/
 │   ├ controllers/
 │   ├ routes/
 │   ├ middlewares/
 │   └ errors/
 ├ domain/
 │   ├ entities/
 │   ├ services/
 │   ├ repositories/
 │   └ errors/
 ├ infrastructure/
 │   ├ db/
 │   ├ redis/
 │   ├ logger/
 │   ├ http/
 │   └ errors/
 └ tests/

MySQL — Connection Pool

ts

import mysql from 'mysql2/promise';

export const pool = mysql.createPool({
  host: process.env.DB_HOST,
  user: process.env.DB_USER,
  database: process.env.DB_NAME,
  password: process.env.DB_PASS,
  connectionLimit: 10,
});

DTO Design Example

json

{
  "searchId": "abc123",
  "key": "flights:BOM:DXB:2025-03-21:hash",
  "flights": [
    {
      "flightNo": "EK501",
      "origin": "BOM",
      "destination": "DXB",
      "duration": "3h 10m",
      "price": 450,
      "currency": "USD",
      "airline": "Emirates"
    }
  ]
}

Redis Caching (5-Minute TTL)

ts

const FLIGHT_TTL_SECONDS = 300;

await redis.set(key, JSON.stringify(dto), {
  EX: FLIGHT_TTL_SECONDS,
  NX: true
});

Expiry UX

On Expiry → user resubmits search.

Swagger / OpenAPI Spec

Documented endpoints:

  • /api/flights/search
  • /api/flights/details

openapi.yaml

openapi: 3.0.3
info:
  title: Flight Search API
  version: 1.0.0
  description: Backend-for-frontend service for searching & retrieving cached flight results.

servers:
  - url: /api

paths:
  /flights/search:
    post:
      summary: Search flights (cached for 5 minutes)
      tags:
        - Flights
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/FlightSearchRequest'
      responses:
        '200':
          description: Search results DTO
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FlightSearchResponse'
        '500':
          description: Internal server error

  /flights/details:
    get:
      summary: Retrieve flight details from Redis cache
      tags:
        - Flights
      parameters:
        - name: key
          in: query
          required: true
          schema:
            type: string
          description: Redis cache key returned by search API
      responses:
        '200':
          description: Cached flight result
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/FlightSearchResponse'
        '404':
          description: Cache expired or key not found
        '500':
          description: Internal server error

components:
  schemas:
    FlightSearchRequest:
      type: object
      required:
        - origin
        - destination
        - date
      properties:
        origin:
          type: string
          example: BOM
        destination:
          type: string
          example: DXB
        date:
          type: string
          format: date
          example: 2025-03-21
        pax:
          type: integer
          example: 1
        cabinClass:
          type: string
          example: ECONOMY

    FlightSearchResponse:
      type: object
      properties:
        searchId:
          type: string
          example: abc123
        key:
          type: string
          example: flights:BOM:DXB:2025-03-21:8f3c0e1a76
        flights:
          type: array
          items:
            $ref: '#/components/schemas/FlightItem'

    FlightItem:
      type: object
      properties:
        flightNo:
          type: string
          example: EK501
        origin:
          type: string
          example: BOM
        destination:
          type: string
          example: DXB
        duration:
          type: string
          example: 3h 10m
        price:
          type: number
          example: 450
        currency:
          type: string
          example: USD
        airline:
          type: string
          example: Emirates

Swagger UI:

app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerDoc));

Swagger UI Setup (Express) ts

import swaggerUi from 'swagger-ui-express'; \
import yaml from 'yamljs';

const swaggerDoc = yaml.load('./openapi.yaml'); \
app.use('/docs', swaggerUi.serve, swaggerUi.setup(swaggerDoc));

Docs available at: bash

/docs

Jest Unit Tests

Cache Hit / Miss

Covers:

  • Cache hit
  • Cache miss
  • TTL set

Centralized Logging & Observability Strategy

Goals

  • Trace every request end-to-end
  • JSON structured logs
  • Query by:
    • user
    • booking
    • redis key
    • trace id
  • Support dashboards & SLIs

Correlation ID Strategy

Every inbound HTTP request receives:

FieldPurpose
requestId Per-request UUID
traceId OpenTelemetry Trace
spanId Span identifier
userId user id
bookingId booking id
redisKey Record Key

These flow through:

Frontend → BFF → Redis → MySQL → Payment → Logs

Redis + MySQL Log Correlation

AttributeExample
redisKeyflights:BOM:DXB:2025-03-21:user123:8f3c0e1a76
BookingIdBkg-9834233
mysqlTxUUID
ttl300 sec

Logging Standards

Log Levels

LevelUsage
tracedev deep debug
debugengineering debug
infobusiness events
warnrecoverable risk
errorfailure
fatalcrash events

Required JSON Fields

bash

timestamp
service
env
requestId
traceId
spanId
userId
BookingId
redisKey
durationMs
status
message

Pino Implementation

Install

bash

npm i pino pino-http pino-pretty

Logger Factory ( /src/infrastructure/logger/logger.ts )

ts

import pino from 'pino';

export const logger = pino({
  level: process.env.LOG_LEVEL || 'info',
  base: {
    service: 'bff-gateway',
    env: process.env.NODE_ENV
  },
  timestamp: pino.stdTimeFunctions.isoTime
});

HTTP Logger ( /src/infrastructure/logger/http-logger.ts )

ts

import pinoHttp from 'pino-http';
import { randomUUID } from 'crypto';
import { logger } from './logger';

export const httpLogger = pinoHttp({
  logger,
  genReqId: (req) =>
    req.headers['x-request-id']?.toString() || randomUUID(),
  customProps: (req) => ({
    userId: req.user?.id,
  })
});

Express Usage

ts

app.use(httpLogger);

app.use((req, res, next) => {
  req.log.info({ path: req.path }, 'request received');
  next();
});

Business Logging Example

ts

req.log.info({
  bkgId,
  redisKey,
  step: 'booking-confirmation'
}, 'Booking confirmed');

OpenTelemetry Tracing

Should attach:

  • traceId
  • spanId

Benefits

  • distributed tracing
  • flame graphs
  • error attribution
Code to link trace → logs → DB query

Security & Compliance

Never log:

  • passwords
  • card numbers
  • tokens
  • PII

Jest Logger Tests

ts

describe('logger', () => {
  it('logs JSON structure', () => {
    const log = logger.info({ test: true }, 'hello');
    expect(log).toBeDefined();
  });
});

Error Handling Strategy

1. Client Errors (4xx)

CodeMeaning
400Bad Request/validation failure
401unauthorized
404cache expired
409idempotency conflict
500internal error

2. Server Errors (5xx)

TypeSourceAction
Dependency FailureRedis/MySQLRetry / Circuit Breaker
Timeout3rd-party APIFail fast
Internal Crashunexpected bugLog & alert

3. Business Errors

ExampleHandling
Flight unavailable409
Payment declined422
Invalid state transition409

1. Business / Domain Errors

When business rule is violeted - /src/domain/errors ts

export class FlightUnavailableError extends Error {
  code = 'FLIGHT_UNAVAILABLE';
}

2. Application Errors

Use-case level
Validation / workflow / orchestration

/src/application/errors ts

export class ValidationError extends Error {
  status = 400;
  code = 'VALIDATION_ERROR';
}

3. Infrastructure Errors

ONLY for wrapping technical failures

/src/infrastructure/errors ts

export class RedisConnectionError extends Error {
  code = 'REDIS_CONNECTION_ERROR';
}

BUT — these should usually be mapped upward into application-level errors.

Example mapping: ts

try {
   await redis.get(key);
} catch(e) {
   throw new CacheUnavailableError();
}

So the API layer never leaks Redis internals.

Error Flow (Best Practice)

Infrastructure error
   ⬇ wrap / map
Application error
   ⬇ serialized to client
HTTP response

final structure should look like

/src
 ┣ /domain
 ┃   ┗ /errors   ← Business rule errors
 ┣ /application
 ┃   ┗ /errors   ← Validation + workflow errors
 ┣ /infrastructure
 ┃   ┗ /errors   ← Technical adapter errors (wrapped)
 ┗ /api (interfaces)
     ┗ error-middleware.ts  ← converts to HTTP response

What to AVOID

Defining all errors in infrastructure
→ couples business logic to technology

Throwing raw DB/Redis errors upward
→ leaks internal details

Mixed styles (sometimes returning strings, sometimes errors)
→ debugging nightmare

flight_booking_bff.txt · Last modified: by madan