Cloudflare Workers-based backend-for-frontend (BFF) that exposes a GraphQL API backed by the upstream gRPC-Web service defined by GRPC_TARGET_ORIGIN. GraphQL resolvers translate queries into protobuf payloads generated from proto/stationapi.proto, forward them via gRPC-Web, and map responses back into the GraphQL schema. Development, testing, and deployment rely on Wrangler v4 and Vitest.
This repository is a monorepo containing two independent Cloudflare Workers:
- Root (
/) — the GraphQL BFF Worker described above (sapi-bff). Its source lives insrc/, configured bywrangler.jsonc. functions/— the TrainLCD backend Worker (trainlcd-worker, formerly Firebase Cloud Functions). It handles TTS synthesis, session issuance, feedback intake/triage, image upload, app config delivery, and hourly review notifications viafetch/queue/scheduledhandlers. It keeps its ownpackage.json,wrangler.jsonc, and toolchain (Biome + Jest); seefunctions/README.mdfor setup, bindings, and deployment.
Each Worker is built, tested, and deployed independently from its own directory. The commands below apply to the root BFF Worker.
- Node.js 18 or later
- npm, pnpm, or another Node.js package manager
- Cloudflare account (required for deployment)
Wrangler is bundled as a devDependency, so npx wrangler works without a global install.
npm installGRPC_TARGET_ORIGIN(required, secret): Origin of the upstream gRPC-Web endpoint, for examplehttps://grpc.example.com. The worker returns HTTP 500 if this value is missing or invalid.GRPC_ALLOWED_ORIGINS(optional): Comma-separated list of origins allowed via CORS. Omit to allow all origins.
For local development set these in .dev.vars (gitignored). For remote environments register GRPC_TARGET_ORIGIN as a secret rather than a vars entry:
wrangler secret put GRPC_TARGET_ORIGIN # staging
wrangler secret put GRPC_TARGET_ORIGIN --env productionConfigure environment-specific values before deploying.
npm run dev- Runs
wrangler devand exposes the worker athttp://localhost:8787/. - The
compatibility_dateis2025-10-11; update it when adopting newer runtime capabilities.
- GraphQL endpoint is exposed at
/graphql(POSTfor queries,GETreturns the SDL). - The runtime schema is declared in
schema.graphqland mirrored insrc/graphqlGateway.ts. POSTalso accepts Apollo ClientBatchHttpLinkpayloads such as[{ query, variables, operationName }], returning results in the same order as received.- Supported queries include:
station(id: Int!)— UsesGetStationByIdRPCstations(ids: [Int!]!)— UsesGetStationByIdListRPCstationsNearby(latitude: Float!, longitude: Float!, limit: Int)— UsesGetStationsByCoordinatesRPCstationsByName(name: String!, limit: Int, fromStationGroupId: Int)— UsesGetStationsByNameRPCstationGroupStations(groupId: Int!)— UsesGetStationByGroupIdRPClineGroupStations(lineGroupId: Int!)— UsesGetStationsByLineGroupIdRPCline(lineId: Int!)— UsesGetLineByIdRPClinesByName(name: String!, limit: Int)— UsesGetLinesByNameRPClineStations(lineId: Int!, stationId: Int)— UsesGetStationsByLineIdRPCstationTrainTypes(stationId: Int!)— UsesGetTrainTypesByStationIdRPCroutes(fromStationGroupId: Int!, toStationGroupId: Int!, pageSize: Int, pageToken: String)— UsesGetRoutesMinimalRPC (optimized with deduplicated line data)routeTypes(fromStationGroupId: Int!, toStationGroupId: Int!, pageSize: Int, pageToken: String)— UsesGetRouteTypesRPCconnectedRoutes(fromStationGroupId: Int!, toStationGroupId: Int!)— UsesGetConnectedRoutesRPC
The routes query uses the GetRoutesMinimal RPC which returns normalized/deduplicated line data for efficient transmission. The BFF automatically reconstructs full Station objects by resolving line references.
Customize or extend the schema by editing schema.graphql and updating the resolvers in src/graphqlGateway.ts.
query Example($from: Int!, $to: Int!) {
routes(fromStationGroupId: $from, toStationGroupId: $to, pageSize: 10) {
routes {
id
stops {
id
name
}
}
nextPageToken
}
}npm test- Uses
cloudflare/testand@cloudflare/vitest-pool-workersto exercise the Worker in a production-like environment, including gRPC-Web framing, CORS handling, and GraphQL execution.
npm run deploy- Run
npx wrangler loginthe first time to authenticate with Cloudflare. - Ensure
GRPC_TARGET_ORIGIN,GRPC_ALLOWED_ORIGINS, and any additional bindings are defined for the target environment prior to deployment.
npm run cf-typegen- Wrangler refreshes
worker-configuration.d.tsso theEnvtypes stay aligned with your bindings.
npm run proto:generate- Compiles
proto/stationapi.prototosrc/generated/stationapi.jsandsrc/generated/stationapi.d.tsusingprotobufjs. - Re-run tests afterwards to ensure the GraphQL↔gRPC translations still behave as expected.
src/index.ts— Worker entry point delegating requests to the GraphQL gateway.src/graphqlGateway.ts— GraphQL schema/resolver wiring, CORS handling, and gRPC-Web invocation helpers.src/generated/stationapi.js— ProtobufJS static module generated fromproto/stationapi.proto.proto/stationapi.proto— Source protocol buffer definitions for the upstream service.schema.graphql— SDL representation of the public GraphQL schema.test/index.spec.ts— Vitest suite covering GraphQL queries and gRPC-Web interactions.vitest.config.mts— Vitest configuration targeting the Workers pool.wrangler.jsonc— Worker metadata, compatibility date, and development/deployment settings.
- Bus-related data provided by Tokyo Metropolitan Bureau of Transportation (Toei), licensed under CC BY 4.0
- Station data provided by 駅データ.jp
This project is released under the MIT License.
