{"record":{"id":"8aeb88bf6a02f42f","repo":"clockworklabs/SpacetimeDB","slug":"route-conflict-for-path","errorCode":null,"errorMessage":"Route conflict for `${path}`","messagePattern":"Route conflict for `(.+?)`","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"crates/bindings-typescript/src/server/http_handlers.ts","lineNumber":355,"sourceCode":"    for (const route of otherRouter.#routes) {\n      merged = merged.addRoute(route.method, route.path, route.handler);\n    }\n    return merged;\n  }\n\n  intoRoutes() {\n    return this.#routes.slice();\n  }\n\n  private addRoute(\n    method: MethodOrAny,\n    path: string,\n    handler: HttpHandlerExport<any>\n  ) {\n    assertValidPath(path);\n    const candidate = { method, path, handler };\n    if (this.#routes.some(route => routesOverlap(route, candidate))) {\n      throw new TypeError(`Route conflict for \\`${path}\\``);\n    }\n    return new Router([...this.#routes, candidate]);\n  }\n}\n\nexport function makeHttpHandlerExport<S extends UntypedSchemaDef>(\n  ctx: SchemaInner,\n  opts: HttpHandlerOpts | undefined,\n  fn: HandlerFn<S>\n): HttpHandlerExport<S> {\n  const handlerExport: HttpHandlerExport<S> = Object.assign(\n    (...args: Parameters<HandlerFn<S>>) => fn(...args),\n    {\n      [httpHandlerFn]: fn,\n      [exportContext]: ctx,\n      [registerExport](ctx: SchemaInner, exportName: string) {\n        if (exportedHttpHandlerObjects.has(handlerExport)) {\n          throw new TypeError(","sourceCodeStart":337,"sourceCodeEnd":373,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/crates/bindings-typescript/src/server/http_handlers.ts#L337-L373","documentation":"Router.addRoute (backing get/post/put/delete/.../any) rejects a candidate that overlaps an existing route: same path string AND matching methods, where 'Any' on either side matches everything, identical methods match, and extension (custom) HTTP methods match by their string value. An overlapping registration throws TypeError('Route conflict for `<path>`') at module build time.","triggerScenarios":"Registering the same method+path twice (two .get('/health', ...)); combining router.any('/x', h) with router.get('/x', h) since Any overlaps every method; registering the same custom extension method value twice on one path.","commonSituations":"Merging routers or files that both define the same route; adding .any() as a fallback after specific handlers on the same path (ambiguous routing is rejected by design); duplicated route constants after copy-paste.","solutions":["Delete or rename the duplicate registration so each method+path pair is unique","Replace a specific route + .any() pair on one path with a single .any() or distinct paths","Search the codebase for the exact path string to find both registration sites, which often live in different modules merged via nest"],"exampleFix":"// before\nrouter.get('/health', healthA);\nrouter.get('/health', healthB); // TypeError: Route conflict\n\n// after\nrouter.get('/health', healthA);\nrouter.get('/status', healthB);","handlingStrategy":"validation","validationCode":"function findRouteConflict(router: Router, method: string, path: string): boolean {\n  return router.intoRoutes().some(\n    r => r.path === path && (r.method.tag === 'Any' || method === 'ANY' || r.method.value === method)\n  );\n}\n// check before adding:\nif (findRouteConflict(router, 'GET', '/health')) throw new Error('duplicate route');","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Define each route exactly once and centralize route registration per path","Avoid mixing .any() with specific methods on the same path","Write a startup assertion that walks intoRoutes() and fails on duplicates with a descriptive message"],"tags":["http","router","routing","typescript"],"backgroundTag":"route-conflict","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}