Skip to content

Latest commit

 

History

25 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Air Horizon Flights Microservices

A REST API for airline inventory and flight search, built with Express and Sequelize on MySQL.

The service owns the reference data an airline booking platform needs (cities, airports, airplanes, seats) and exposes a filterable flight-search endpoint plus a concurrency-safe seat inventory operation.


Microservices

  1. Flight Booking Service
  2. Backend API Gateway
  3. Airline Notification Service

Architecture

alt text

The service follows a strict layered flow. Each layer talks only to the layer directly beneath it.

HTTP request
    |
    v
 Router          src/routes/         URL to handler mapping, API versioning
    |
    v
 Middleware      src/middlewares/    Request-shape validation, short-circuits with 400
    |
    v
 Controller      src/controllers/    HTTP boundary: reads req, calls service, writes res
    |
    v
 Service         src/services/       Business rules, cross-entity validation, error mapping
    |
    v
 Repository      src/repositories/   All data access; the only layer that knows Sequelize
    |
    v
 Model           src/models/         Schema definitions and associations
    |
    v
 MySQL

---

Data model

alt text


Getting started

1. Install dependencies

npm install

2. Create the environment file

Create .env at the project root:

PORT=3000

3. Configure the database

Create src/config/config.json (see Configuration for the template) and create the target schema in MySQL:

CREATE DATABASE flights_dev;

4. Run migrations and seed data

cd src
npx sequelize db:migrate
npx sequelize db:seed:all
cd ..

5. Start the server

npm run dev

Verify the service is up:

curl http://localhost:3000/api/v1/info
{ "success": true, "message": "API is live", "error": {}, "data": {} }

Configuration

Environment variables

Variable Required Default Description
PORT yes none HTTP port the server binds to
NODE_ENV no development Selects the block used from src/config/config.json

Database configuration

src/config/config.json holds credentials and is gitignored. Create it from this template:

{
  "development": {
    "username": "root",
    "password": "your-password",
    "database": "flights_dev",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "test": {
    "username": "root",
    "password": "your-password",
    "database": "flights_test",
    "host": "127.0.0.1",
    "dialect": "mysql"
  },
  "production": {
    "username": "root",
    "password": "your-password",
    "database": "flights_prod",
    "host": "your-db-host",
    "dialect": "mysql"
  }
}

Never commit this file. .gitignore already excludes .env, node_modules/, combine.log, and src/config/config.json.


Database migrations and seeding

Migrations and seeders live under src/, and there is no .sequelizerc at the root, so all sequelize-cli commands must be run from inside src/.

Command Purpose
npx sequelize db:migrate Apply all pending migrations
npx sequelize db:migrate:undo Roll back the most recent migration
npx sequelize db:migrate:undo:all Roll back everything
npx sequelize db:seed:all Run every seeder
npx sequelize migration:generate --name <name> Scaffold a new migration
npx sequelize seed:generate --name <name> Scaffold a new seeder

Migration order matters and is encoded in the filename timestamps:

  1. create-airplane
  2. create-city
  3. create-airport
  4. update-city-airport-association — adds the city_fkey_constraint foreign key
  5. create-flight
  6. create-seat

Every migration implements a working down, so the schema is reversible.


API reference

All routes are prefixed with /api/v1. A placeholder /api/v2/info exists for the next contract version.

Service

Method Endpoint Description
GET /api/v1/info Liveness check
GET /api/v2/info v2 placeholder

Cities

Base path: /api/v1/city

Method Endpoint Body Description
POST /city { name } Create a city. name is required and unique
GET /city List all cities
GET /city/:id Fetch one city
PATCH /city/:id { name } Update a city
DELETE /city/:id Delete a city and cascade to its airports
curl -X POST http://localhost:3000/api/v1/city \
  -H "Content-Type: application/json" \
  -d '{"name":"Mumbai"}'

Airports

Base path: /api/v1/airports

Method Endpoint Body Description
POST /airports { name, code, address, cityId } Create an airport. All four fields are required; name and code are unique
GET /airports List all airports
GET /airports/:id Fetch one airport
PATCH /airports/:id partial Update an airport
DELETE /airports/:id Delete an airport
curl -X POST http://localhost:3000/api/v1/airports \
  -H "Content-Type: application/json" \
  -d '{"name":"Chhatrapati Shivaji International","code":"MUM","address":"Andheri East, Mumbai","cityId":1}'

Airplanes

Base path: /api/v1/airplanes

Method Endpoint Body Description
POST /airplanes { modelNumber, capacity } Create an airplane. modelNumber is required; capacity must not exceed 1200
GET /airplanes List all airplanes
GET /airplanes/:id Fetch one airplane
PATCH /airplanes/:id { modelNumber, capacity } Update an airplane
DELETE /airplanes/:id Delete an airplane and cascade to its flights and seats
curl -X POST http://localhost:3000/api/v1/airplanes \
  -H "Content-Type: application/json" \
  -d '{"modelNumber":"airbus-a380","capacity":400}'

Flights

Base path: /api/v1/flights

Method Endpoint Description
POST /flights Create a flight
GET /flights Search flights with filters and sorting
GET /flights/:id Fetch one flight
PATCH /flights/:id/seats Decrement or increment remaining seats

Create a flight

Required fields: flightNumber, airplaneId, departureAirportId, arrivalAirportId, arrivalTime, departureTime, price, totalSeats.

curl -X POST http://localhost:3000/api/v1/flights \
  -H "Content-Type: application/json" \
  -d '{
    "flightNumber": "UK-808",
    "airplaneId": 1,
    "departureAirportId": "MUM",
    "arrivalAirportId": "DEL",
    "departureTime": "2026-09-01 09:00:00",
    "arrivalTime": "2026-09-01 11:10:00",
    "price": 4500,
    "boardingGate": "12A",
    "totalSeats": 180
  }'

Business rules enforced by the service layer:

  • Arrival time must be strictly later than departure time.
  • Departure and arrival airports must differ.

Search flights

Query parameter Format Example Effect
trips <DEP>-<ARR> trips=MUM-DEL Filters by departure and arrival airport codes
price <min>-<max> price=2000-8000 Price band. Omitting the max defaults it to 20000
travellers integer travellers=4 Only flights with at least this many remaining seats
tripDate YYYY-MM-DD tripDate=2026-09-01 Departures within that calendar day
sort <field>_<dir>,... sort=price_ASC,departureTime_DESC Multi-column ordering
curl "http://localhost:3000/api/v1/flights?trips=MUM-DEL&price=2000-8000&travellers=2&tripDate=2026-09-01&sort=price_ASC,departureTime_DESC"

Results are returned fully hydrated: each flight embeds airplaneDetail, plus departureAirport and arrivalAirport, each with its parent City. The airport joins are custom ON clauses matching Flight.departureAirportId against Airport.code rather than the default numeric key.

Update remaining seats

curl -X PATCH http://localhost:3000/api/v1/flights/1/seats \
  -H "Content-Type: application/json" \
  -d '{"seats":2,"dec":true}'
Field Type Required Description
seats integer yes Number of seats to move
dec boolean no (defaults to true) true books seats, false releases them

Response contract

Every endpoint returns the same envelope, so clients need exactly one parser.

Success

{
  "success": true,
  "message": "Successfully completed the request",
  "data": { },
  "error": { }
}

Failure

{
  "success": false,
  "message": "Something went wrong",
  "data": { },
  "error": {
    "explanation": ["The incoming request does not contain a valid flightNumber."],
    "statusCode": 400
  }
}

Error handling

Errors are classified where the most context exists and then translated once:

Origin Handling
Missing or malformed request fields Middleware returns 400 before the controller runs
SequelizeValidationError / SequelizeUniqueConstraintError Service collects every err.message into an array and raises 400
Record not found CrudRepository raises 404; the service rewrites it with an entity-specific message
Anything else Service raises 500 with a generic message so internals are not leaked
Status Meaning
200 OK Read, update, or delete succeeded
201 Created Resource created
400 Bad Request Validation failure, in error.explanation
404 Not Found No record for the supplied id
500 Internal Server Error Unexpected failure

Concurrency with Transactions & Pessimistic Locking

A naive read-modify-write lets two simultaneous bookings both read 5 remaining seats, both subtract 3, and both succeed — overselling the flight.

FlightRepository.updateRemainingSeats prevents this with a transaction plus a pessimistic row lock taken before the read:

SELECT * FROM flights WHERE flights.id = ? FOR UPDATE

The lock is held until the transaction commits or rolls back, so a second request for the same flight blocks instead of reading stale data, and any failure rolls back rather than leaving the seat count half-updated.


Logging

Winston is configured in src/config/logger-config.js with timestamped output to two transports:

  • Console — for the local development loop.
  • File (combine.log) — for after-the-fact inspection. It is gitignored.
logger.info("Successfully started the server", "root", {});

About

Air Horizon is a backend microservice built using a microservice architecture, responsible for managing core flight-domain operations including flights, airports, and cities. Designed with a focus on scalability, reliability, and maintainability, it provides well-structured APIs to support efficient airline operations.

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages