Split the 5,441-line index.js into an 86-line bootstrap, thin domain
route modules, shared lib/ helpers, and a repo/ layer that isolates the
DEV_MOCK branches (repo/*.mock.js). Repo methods return {statusCode, body};
routes are one-line wrappers. Pin search_path to "transit.bus" and
document the /bus/ prefix with legacy root aliases (Deprecation header).
Parity verified: 57/57 byte-identical on the stub differential harness
plus 24/24 live read-only responses vs the original against real
PostgreSQL. The only intentional behavior change: mock reorder/delete
bus-line endpoints no longer require a live DB (previously 500 in
DEV_MOCK=1 without PG). DEV_MOCK gate and node --check clean.
8.7 KiB
Transit API
Transit API for managing bus routes, navigation, and GPS tracking for bus drivers.
Project Structure
transit_api/
├── index.js # Main API endpoints
├── middleware.js # JWT authentication middleware
├── db.js # PostgreSQL connection
├── schema.sql # Database schema setup
├── package.json # Dependencies
└── .env # Environment variables
Setup
1. Install Dependencies
npm install
2. Set Up Database Schema
Execute the following in PostgreSQL:
# Via psql command line
psql -U your_user -d your_database -f schema.sql
Or copy-paste the SQL queries directly into pgAdmin.
3. Environment Variables (.env)
PG_HOST=localhost
PG_PORT=5432
PG_USER=your_user
PG_PASS=your_password
PG_DB=your_database
JWT_SECRET=your_jwt_secret
PORT=5001
Note: ADMIN_USER_CODES has been moved to the auth_api service. Configure admin user codes in auth_api/.env as a comma-separated list.
4. Start API
npm start
The API runs at https://transit.xtransit.testinstance.nl
The service group also includes the Employee API at
https://employees.xtransit.testinstance.nl and the central monitoring
dashboard. See ../MONITORING_UI.md for health
checks, error inspection, and the controlled request tester.
API Endpoints
All endpoints require an Authorization: Bearer {token} header.
GET /health
- Checks whether the deployed transit API is running and the database is reachable.
- Returns
200with{"status":"ok","database":"ok"}on success. - Returns
503if the API is running but the database is unreachable.
Schedule
Schedules are weekly templates. Each row in "transit.bus".schedules represents one
departure time for one bus line and one day_of_week:
1to5: weekdays6: Saturday0: Sunday
Driver assignments are stored separately in "transit.bus".shifts with
(schedule_id, driver_id), where driver_id references workers.users(id).
The current shifts shape represents the active assignment. To retain different
assignments for different calendar weeks, add a week_start (or assignment date
range) column and include it in the unique key.
GET /bus/driver/schedules/daily?date=2026-09-18
- Returns only weekly schedule templates assigned to the authenticated driver through
"transit.bus".shifts.
PATCH /bus/driver/weekly-schedules/:scheduleId/status
- Updates the status of an assigned weekly schedule.
GET /bus/admin/weekly-schedules
- Lists weekly templates and their current driver assignments.
GET /bus/admin/shift-drivers
- Lists drivers from
workers.usersfor shift assignment.
POST /bus/admin/weekly-schedules
- Creates one template. Body:
{ "busLineId": 1, "dayOfWeek": 1, "startTime": "08:00", "endTime": "09:00", "driverId": 12 }.
POST /bus/admin/weekly-schedules/bulk
- Creates multiple departures in one transaction. The API derives each end time
from the selected bus line duration. Body:
{ "busLineId": 1, "dayOfWeeks": [1,2,3,4,5], "startTimes": ["06:00", "06:30", "07:00"], "driverId": 12 }.
PUT /bus/admin/weekly-schedules/:scheduleId/shift
- Replaces the driver assignment. Pass
{ "driverId": 12 }or{ "driverId": null }.
The legacy /admin/schedules and /schedule/daily aliases remain for older
clients, but new clients must use the /bus/-prefixed canonical endpoints
(/bus/admin/schedules, /bus/schedule/daily) and the weekly schedule
endpoints above.
GET /bus/schedule/daily?date=2024-05-05
- Retrieve daily schedule for driver
- Response:
{
"date": "2024-05-05",
"routes": [
{
"id": 1,
"startTime": "2024-05-05T08:00:00Z",
"status": "scheduled",
"busLine": {
"lineNumber": 1,
"startStop": "Central Station",
"endStop": "Airport"
},
"busType": {
"name": "Standard Bus",
"seatCapacity": 50,
"licensePlate": "BUS-001"
}
}
]
}
GET /bus/routes/:routeId
- Details of a specific route
- Response:
{ "route": {...} }
Public Schedule Overview
GET /bus/schedules/overview?date=2026-05-06&lineId=1
- Fetches a date-based overview of scheduled services
- Weekly templates are selected by
day_of_weekand driver assignments byshifts - One-off / incidental services are only included on their exact date
- Filters:
driverId/chauffeurId,stopId/busStopId,lineId,status - Response:
{ "date": "2026-05-06", "summary": {...}, "schedules": [...] }
The legacy endpoint GET /schedules remains available as a deprecated alias
for existing clients; the canonical path is GET /bus/schedules.
Example:
curl "https://transit.xtransit.testinstance.nl/bus/schedules/overview?date=2026-05-06&chauffeurId=12&busStopId=3"
Navigation
POST /bus/navigation/route
- Save calculated route from OSRM
- Body:
{
"routeId": 1,
"startLat": 52.5200,
"startLon": 13.4050,
"endLat": 52.5300,
"endLon": 13.4150,
"totalDistance": 15000,
"totalDuration": 900,
"geojson": {
"type": "LineString",
"coordinates": [[13.4050, 52.5200], ...]
}
}
GET /bus/bus-lines/:busLineId/stops
- All stops of a bus line (for navigation waypoints)
- Response:
{
"stops": [
{
"id": 1,
"order": 1,
"name": "Central Station",
"latitude": 52.52,
"longitude": 13.405
}
]
}
GPS Tracking
POST /bus/gps/track
- Save real-time position
- Body:
{
"routeId": 1,
"latitude": 52.521,
"longitude": 13.406,
"speed": 45.5,
"heading": 180,
"accuracy": 5.0
}
GET /bus/gps/route/:routeId
- All recorded position points of a route
- Response:
{
"positions": [
{
"id": 1,
"latitude": 52.521,
"longitude": 13.406,
"speed": 45.5,
"heading": 180,
"recordedAt": "2024-05-05T08:15:00Z"
}
]
}
Route Status
PATCH /bus/routes/:routeId/status
- Update route status
- Body:
{ "status": "in_progress" } - Valid statuses:
scheduled,in_progress,completed,cancelled
Admin
All admin endpoints require, in addition to a valid JWT token, that your userCode is present in ADMIN_USER_CODES.
POST /bus/admin/stops
- Create a reusable stop
- Body:
{
"name": "Central Station",
"latitude": 52.52,
"longitude": 13.405
}
POST /bus/admin/bus-lines/:busLineId/stops
- Link an existing stop to a bus line
- Body:
{
"stopId": 1,
"stopOrder": 1,
"estimatedArrivalMinutes": 0
}
Database Schema
bus_types
id- Primary keyname- Bus type name (Standard Bus, etc.)seat_capacity- Number of seatslicense_plate- Unique license plate
bus_lines
id- Primary keyline_number- Unique line number (1, 5, 12, etc.)start_stop- Start stop nameend_stop- End stop nameestimated_duration_minutes- Expected route duration
stops
id- Primary keyname- Stop namelatitude,longitude- Stop location
route_stops
id- Primary keybus_line_id- Which bus line the stop belongs tostop_id- Reusable stop referencestop_order- Stop sequence on that bus lineestimated_arrival_minutes- Optional ETA per stop
routes
id- Primary keyuser_id- Driver (references workers.users)bus_line_id- Which line (references bus_lines)bus_type_id- Which bus (references bus_types)start_time- Scheduled startstatus- scheduled/in_progress/completed/cancelled
navigation_routes
id- Primary keyroute_id- Link to route (unique)polyline_geojson- GeoJSON LineString of the routetotal_distance_meters- Route distancetotal_duration_seconds- Estimated duration
gps_tracking
id- Primary keyroute_id- Which routelatitude,longitude- Current positionspeed_kmh- Current speedrecorded_at- Timestamp
Integration with Flutter App
// In schedule_service.dart
// Save navigation route
await scheduleService.saveNavigationRoute(
routeId: 1,
navRoute: navigationRoute,
);
// Track GPS
await scheduleService.trackGPS(
routeId: 1,
currentPos: navigationPoint,
speed: 45.5,
heading: 180,
);
// Update route status
await scheduleService.updateRouteStatus(1, 'in_progress');
Error Handling
All endpoints return error responses:
{
"error": "error_code",
"details": "More information"
}
Common errors:
missing_token- No Authorization headerinvalid_token- Token verification failedmissing_required_fields- Required data missinginvalid_status- Invalid status valueroute_not_found- Route not foundunauthorized- User doesn't own this resource