Skip to content

jaredkirkley/hotel

Repository files navigation

hotel-discovery-api

A REST API for searching and browsing hotel inventory. Built with Node.js 22, Fastify 5, and TypeScript.


Prerequisites

  • Node.js 22 LTS (lts/jod) — use nvm to manage versions
  • npm (bundled with Node)
  • Docker + Docker Compose (optional, for containerized local dev)

Local Development

1. Install dependencies

nvm use          # picks up .nvmrc → Node 22
npm install

2. Configure environment

cp .env.example .env
# Edit .env if you need non-default values (port, log level, etc.)

3. Start the dev server

npm run dev

The server starts with hot-reload via tsx watch. Default: http://localhost:3000.

Verify it's up:

curl http://localhost:3000/health
# {"status":"ok","uptime":0.123,"timestamp":"..."}

Running with Docker

docker compose up

This builds the image using the builder stage (full dev dependencies, tsx watch for hot-reload) and mounts ./src into the container so edits reflect immediately without a rebuild.

The health check polls GET /health every 10 seconds.

To build and run the production image instead:

docker build -t hotel-discovery-api .
docker run -p 3000:3000 -e NODE_ENV=production hotel-discovery-api

Running Tests

npm test

Uses Node's built-in node:test runner. No third-party test framework.

Coverage

npm run test:coverage

Uses c8 (V8 native coverage). Outputs a text summary and an lcov report in coverage/.

Type checking

npm run typecheck

Runs tsc --noEmit — no output files, just compile-time validation.


Building for Production

npm run build          # compiles TypeScript → dist/
npm start              # runs dist/index.js

Deploying to Kubernetes

The Helm chart lives in ci/helm/. It supports two environments via value overrides:

# Dev
helm upgrade --install hotel-discovery-api ./ci/helm \
  -f ci/helm/values.dev.yaml \
  --set image.tag=$(git rev-parse --short HEAD)

# Prod
helm upgrade --install hotel-discovery-api ./ci/helm \
  -f ci/helm/values.prod.yaml \
  --set image.tag=$(git rev-parse --short HEAD)

Update ci/helm/values.dev.yaml and ci/helm/values.prod.yaml with your actual image repository and ingress hostnames before deploying.


Environment Variables

Variable Default Description
NODE_ENV development production disables dev-only behavior
PORT 3000 Port the HTTP server listens on
HOST 0.0.0.0 Bind address
LOG_LEVEL info Pino log level (debug, info, warn)

API Reference

All endpoints under /api/v1 require an Authorization: Bearer <token> header. Token content is not validated in the current build — see ASSUMPTIONS.md for context.

Infrastructure endpoints (/health, /ready) are unauthenticated.

Infrastructure

Method Path Description
GET /health Liveness probe — always 200
GET /ready Readiness probe — always 200

Hotels

GET /api/v1/hotels

Search and filter hotels. Returns a paginated list of hotel summaries (rooms omitted, room_count included).

Query parameters (all optional):

Parameter Type Description
city string Case-insensitive exact match on city name
state string Case-insensitive exact match on state / region
country string Case-insensitive exact match on country name
star_rating integer Exact star rating (1–5)
min_rating number Minimum overall_rating (inclusive)
amenity string Hotel must have this amenity; repeat for AND logic
min_price number Hotel must have ≥1 room with price_per_night ≥ this value
max_price number Hotel must have ≥1 room with price_per_night ≤ this value
check_in string ISO date (YYYY-MM-DD); must be paired with check_out
check_out string ISO date (YYYY-MM-DD); hotel must have ≥1 room available every night
limit integer Page size (1–100, default 20)
offset integer Page offset (default 0)

Response 200:

{
  "data": [
    {
      "id": "hotel-01",
      "name": "The Grand Luminary",
      "star_rating": 5,
      "overall_rating": 4.8,
      "review_count": 1240,
      "address": { "city": "Chicago", "state": "IL", "country": "USA", "..." : "..." },
      "amenities": ["pool", "free Wi-Fi", "spa"],
      "room_count": 2
    }
  ],
  "meta": { "total": 40, "limit": 20, "offset": 0 }
}

Errors:

  • 400star_rating out of 1–5 range, or invalid date range (only one of check_in/check_out provided, or check_in >= check_out)
  • 401 — missing or malformed Authorization header

Note: Unknown query parameters are silently stripped by Fastify 5 rather than rejected. See ASSUMPTIONS.md for details.


GET /api/v1/hotels/:id

Retrieve full details for a single hotel, including all room definitions.

Response 200: Full hotel object with rooms array.

Errors:

  • 401 — missing or malformed Authorization header
  • 404 — hotel not found

GET /api/v1/hotels/:id/rooms

Retrieve available rooms and pricing for a hotel, optionally scoped to a specific stay.

Query parameters (all optional):

Parameter Type Description
check_in string ISO date (YYYY-MM-DD); must be paired with check_out
check_out string ISO date (YYYY-MM-DD, exclusive — the departure day)
max_price number Maximum price_per_night
min_occupancy integer Room must accommodate at least this many guests
bed_type string Case-insensitive match (King, Queen, Double, Full, Twin, Futon)

When check_in and check_out are provided:

  • Only rooms available for every night of the stay are returned.
  • Each room in the response includes a computed total_price field (price_per_night × nights).
  • The meta block includes check_in, check_out, and nights.

Response 200:

{
  "data": [
    {
      "room_id": "room-01a",
      "type": "Deluxe King Room",
      "bed_type": "King",
      "bed_count": 1,
      "max_occupancy": 2,
      "square_footage": 450,
      "price_per_night": 299.00,
      "total_price": 598.00,
      "room_amenities": ["city_view", "mini_bar"],
      "available_dates": ["2026-07-10", "2026-07-11", "2026-07-12"]
    }
  ],
  "meta": {
    "total": 1,
    "check_in": "2026-07-10",
    "check_out": "2026-07-12",
    "nights": 2
  }
}

Errors:

  • 400 — only one of check_in/check_out provided, or check_in >= check_out
  • 401 — missing or malformed Authorization header
  • 404 — hotel not found

GET /api/v1/hotels/:id/rooms/:roomId

Retrieve a single room by ID within a hotel.

Response 200: Full room object including available_dates.

Errors:

  • 401 — missing or malformed Authorization header
  • 404 — hotel or room not found

Architecture

Stack

  • Runtime: Node.js 22 LTS (ESM, "type": "module")
  • HTTP framework: Fastify 5 — chosen for native TypeScript support, JSON Schema validation, and fast serialization
  • Language: TypeScript 5 in strict mode
  • Testing: Node built-in node:test + c8 for coverage

Project layout

src/
  config.ts          Environment variable loading with fail-fast validation
  app.ts             Fastify instance factory (plugins, routes)
  index.ts           Entrypoint — binds the server to the configured port
  plugins/
    auth.ts          Bearer token gate (stub — see ASSUMPTIONS.md)
  routes/
    health.ts        /health and /ready — no auth
    hotels.ts        All /api/v1/hotels/** routes
    index.ts         Route registration and prefix wiring
  data/
    hotels.ts        Static hotel dataset (40 properties)
  types/
    index.ts         Shared TypeScript types
test/
  health.test.ts
  hotels.test.ts
ci/
  helm/              Kubernetes Helm chart (dev + prod value sets)

Data layer

There is no database. All data is served from an in-memory TypeScript array (src/data/hotels.ts) backed by a Map for O(1) ID lookups. See ASSUMPTIONS.md for the reasoning.

Auth

All /api/v1/** routes are protected by a verifyToken preHandler. The current implementation checks that an Authorization: Bearer <token> header is present and returns 401 if it is not. Token content is intentionally not validated — the plugin is structured to make plugging in real JWT/JWKS verification a single-file change. See src/plugins/auth.ts and ASSUMPTIONS.md.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors