CLANKER ALARM!!!
Find a file
2026-07-10 10:54:05 +02:00
public init 2026-07-10 10:54:05 +02:00
src init 2026-07-10 10:54:05 +02:00
.gitignore init 2026-07-10 10:54:05 +02:00
flake.lock init 2026-07-10 10:54:05 +02:00
flake.nix init 2026-07-10 10:54:05 +02:00
package-lock.json init 2026-07-10 10:54:05 +02:00
package.json init 2026-07-10 10:54:05 +02:00
README.md init 2026-07-10 10:54:05 +02:00
tsconfig.json init 2026-07-10 10:54:05 +02:00

MultiTrain

Search train connections from multiple start stations to multiple destination stations in one query. Results are merged, deduplicated, and ranked; each station can carry a penalty in minutes (e.g. "Wien Meidling +10 because it takes me longer to get there") that is added to a journey's duration for ranking only — displayed times stay real.

Data comes from Transitous, a community-run MOTIS instance aggregating open timetable data from many carriers (ÖBB, DB, and many more). Please respect their fair-use policy: set a USER_AGENT that identifies you.

Development

nix develop        # provides node 22 + npm + typescript-language-server
npm install
npm run dev        # dev server on http://127.0.0.1:3000

Note: tsx watch does not detect file changes on Windows-mounted drives (/mnt/* in WSL) — restart manually there.

Architecture

Lightweight hexagonal: src/model is the pure domain core (domain model, JourneySource port, search use case — fan-out over station pairs, merge, dedupe, penalty scoring). src/adapter holds the Fastify HTTP adapter and the Transitous adapter (the only place touching the MOTIS client libraries). src/main.ts wires everything by hand.

API

  • GET /api/locations?query=wien — station suggestions

  • POST /api/search — body:

    {
      "from": [{ "query": "Wien Hbf", "penalty": 0 }, { "query": "Wien Meidling", "penalty": 10 }],
      "to": [{ "query": "München Hbf", "penalty": 0 }],
      "departure": "2026-07-11T08:00:00Z"
    }
    

    departure and arrival are mutually exclusive; omit both to depart now.

Configuration (environment)

Variable Default Purpose
PORT 3000 HTTP port
HOST 127.0.0.1 Bind address
USER_AGENT multitrain Sent to the MOTIS API — please personalize
MOTIS_BASE_URL api.transitous.org Self-hosted MOTIS instance

Deployment on NixOS

# flake inputs
inputs.multitrain.url = "github:you/multitrain"; # or a path/git url

# NixOS configuration
{
  imports = [ inputs.multitrain.nixosModules.default ];

  services.multitrain = {
    enable = true;
    port = 3000;
    host = "127.0.0.1";
    userAgent = "multitrain (you@example.org)";
    # motisBaseUrl = "https://motis.example.org";
  };
}

nix build produces the server as result/bin/multitrain. After changing package-lock.json, refresh npmDepsHash in flake.nix (nix run nixpkgs#prefetch-npm-deps -- package-lock.json).