@@ -85,6 +85,7 @@ export interface RouteInfo {
8585 handler : RouteHandler ; // Store the handler
8686 metadata ?: RouteMetadata ; // Middleware metadata
8787 implicitHead ?: boolean ; // Auto-registered HEAD fallback for GET routes
88+ trailingSlash ?: boolean ; // Auto-registered trailing-slash variant for Express compatibility
8889}
8990
9091/**
@@ -252,6 +253,11 @@ export class RouteRegistry {
252253 } ;
253254
254255 this . routes . set ( routeKey , routeInfo ) ;
256+ // Also update trailing-slash variant if it exists
257+ const slashRouteKey = `${ normalizedMethod } :${ path } /` ;
258+ if ( this . routes . has ( slashRouteKey ) ) {
259+ this . routes . set ( slashRouteKey , { ...routeInfo , trailingSlash : true } ) ;
260+ }
255261 if ( isComplex ) {
256262 const staticPrefix = this . extractStaticPrefix ( path ) ;
257263 const registrationPath = staticPrefix ? `${ staticPrefix } /*` : '/*' ;
@@ -385,11 +391,9 @@ export class RouteRegistry {
385391 this . complexRoutesByWildcard . get ( wildcardKey ) ! . push ( routeInfo ) ;
386392 } else {
387393 // Simple route - use native uWS routing
388- uwsMethodFn . call (
389- this . uwsApp ,
390- uwsPath ,
391- async ( uwsRes : uWS . HttpResponse , uwsReq : uWS . HttpRequest ) => {
392- const activeRoute = this . routes . get ( routeKey ) ! ;
394+ const createHandler =
395+ ( key : string ) => async ( uwsRes : uWS . HttpResponse , uwsReq : uWS . HttpRequest ) => {
396+ const activeRoute = this . routes . get ( key ) ! ;
393397
394398 // Create request/response wrappers
395399 const req = new UwsRequest ( uwsReq , uwsRes , paramNames ) ;
@@ -414,8 +418,26 @@ export class RouteRegistry {
414418
415419 // Execute handler with error handling
416420 await this . executeHandler ( activeRoute . handler , req , res , activeRoute . metadata ) ;
421+ } ;
422+
423+ uwsMethodFn . call ( this . uwsApp , uwsPath , createHandler ( routeKey ) ) ;
424+
425+ // Also register trailing-slash variant for Express compatibility
426+ // (e.g. /api and /api/ should both match the same route)
427+ if ( ! uwsPath . endsWith ( '/' ) && uwsPath !== '/' ) {
428+ const slashRouteKey = `${ normalizedMethod } :${ path } /` ;
429+ if ( ! this . routes . has ( slashRouteKey ) ) {
430+ this . routes . set ( slashRouteKey , { ...routeInfo , trailingSlash : true } ) ;
431+ uwsMethodFn . call ( this . uwsApp , uwsPath + '/' , createHandler ( slashRouteKey ) ) ;
417432 }
418- ) ;
433+ } else if ( uwsPath . endsWith ( '/' ) && uwsPath !== '/' ) {
434+ const nonTrailingPath = uwsPath . slice ( 0 , - 1 ) ;
435+ const companionKey = `${ normalizedMethod } :${ path . slice ( 0 , - 1 ) } ` ;
436+ if ( ! this . routes . has ( companionKey ) ) {
437+ this . routes . set ( companionKey , { ...routeInfo , trailingSlash : false } ) ;
438+ uwsMethodFn . call ( this . uwsApp , nonTrailingPath , createHandler ( companionKey ) ) ;
439+ }
440+ }
419441 }
420442
421443 if ( normalizedMethod === 'GET' && ! implicitHead ) {
@@ -935,7 +957,11 @@ export class RouteRegistry {
935957 * @returns Map of route keys to route information
936958 */
937959 getRoutes ( ) : Map < string , RouteInfo > {
938- return new Map ( [ ...this . routes ] . filter ( ( [ , route ] ) => ! route . implicitHead ) ) ;
960+ return new Map (
961+ [ ...this . routes ] . filter (
962+ ( [ , route ] ) => ! route . implicitHead && route . trailingSlash === undefined
963+ )
964+ ) ;
939965 }
940966
941967 /**
@@ -957,7 +983,9 @@ export class RouteRegistry {
957983 * @returns Number of registered routes
958984 */
959985 getRouteCount ( ) : number {
960- return [ ...this . routes . values ( ) ] . filter ( ( route ) => ! route . implicitHead ) . length ;
986+ return [ ...this . routes . values ( ) ] . filter (
987+ ( route ) => ! route . implicitHead && route . trailingSlash === undefined
988+ ) . length ;
961989 }
962990
963991 private replaceComplexRoute ( wildcardKey : string , routeInfo : RouteInfo ) : void {
0 commit comments