{"record":{"id":"f066af88b129cda3","repo":"clockworklabs/SpacetimeDB","slug":"route-paths-may-contain-only-acceptable-route-pa","errorCode":null,"errorMessage":"Route paths may contain only ${ACCEPTABLE_ROUTE_PATH_CHARS_HUMAN_DESCRIPTION}: ${path}","messagePattern":"Route paths may contain only (.+?): (.+?)","errorType":"exception","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"crates/bindings-typescript/src/server/http_handlers.ts","lineNumber":107,"sourceCode":"}\n\nfunction characterIsAcceptableForRoutePath(c: string) {\n  return (\n    (c >= 'a' && c <= 'z') ||\n    (c >= '0' && c <= '9') ||\n    c === '-' ||\n    c === '_' ||\n    c === '~' ||\n    c === '/'\n  );\n}\n\nfunction assertValidPath(path: string) {\n  if (path !== '' && !path.startsWith('/')) {\n    throw new TypeError(`Route paths must start with \\`/\\`: ${path}`);\n  }\n  if (![...path].every(characterIsAcceptableForRoutePath)) {\n    throw new TypeError(\n      `Route paths may contain only ${ACCEPTABLE_ROUTE_PATH_CHARS_HUMAN_DESCRIPTION}: ${path}`\n    );\n  }\n}\n\nfunction routesOverlap(a: RouteSpec, b: RouteSpec) {\n  const methodsMatch = (left: HttpMethod, right: HttpMethod) => {\n    if (left.tag !== right.tag) {\n      return false;\n    }\n    if (left.tag === 'Extension' && right.tag === 'Extension') {\n      return left.value === right.value;\n    }\n    return true;\n  };\n\n  return (\n    a.path === b.path &&","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/clockworklabs/SpacetimeDB/blob/524b4487d949b61a07d4f39c862d1290259dfd20/crates/bindings-typescript/src/server/http_handlers.ts#L89-L125","documentation":"assertValidPath also enforces a strict character whitelist for route paths: lowercase a-z, digits 0-9, '-', '_', '~' and '/'. These are the RFC 3986 unreserved characters, so routes match without percent-decoding ambiguity. Any other character (uppercase letters, ':', '{', '.', '*', spaces) throws a TypeError listing the allowed set.","triggerScenarios":"Writing REST-style parameterized paths like '/users/:id' or '/users/{id}' (path parameters are not supported by this router); camelCase or uppercase segments such as '/api/Users'; extension routes like '/sitemap.xml'.","commonSituations":"Coming from Express/Koa/Fastify where ':param' syntax is standard; generated route tables containing uppercase or dots; assuming dynamic path segments exist because other frameworks offer them.","solutions":["Replace path parameters with a static segment and read identifiers from the query string or request body inside the handler","Lowercase the whole path and strip unsupported characters","If per-id routes are required, parse ctx.request.uri inside one handler and dispatch manually"],"exampleFix":"// before\nrouter.get('/users/:id', getUser);\n\n// after\nrouter.get('/users', getUser); // read ?id=... from the query string inside the handler","handlingStrategy":"validation","validationCode":"const ROUTE_PATH_RE = /^[a-z0-9\\-_~/]*$/;\nfunction isValidRoutePath(path: string): boolean {\n  return (path === '' || path.startsWith('/')) && ROUTE_PATH_RE.test(path);\n}\n// reject early with a clear message:\nif (!isValidRoutePath(p)) throw new Error(`bad route path: ${p}`);","typeGuard":"const isStaticRoutePath = (p: string): p is string =>\n  (p === '' || p.startsWith('/')) && /^[a-z0-9\\-_~/]*$/.test(p);","tryCatchPattern":null,"preventionTips":["Remember this router has no path parameters: design identifiers as query strings or body fields","Keep route paths lowercase and limited to unreserved characters from the start","Lint route definitions in CI with the same regex the runtime uses"],"tags":["http","router","validation","typescript"],"backgroundTag":"invalid-route-path","analyzedSha":"524b4487d949b61a07d4f39c862d1290259dfd20","analyzedAt":"2026-08-16T23:58:54.611Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}