Skip to content
28 changes: 28 additions & 0 deletions docs/api/app.md
Original file line number Diff line number Diff line change
Expand Up @@ -573,3 +573,31 @@ Hooks is a method to return [hooks](./hooks.md) property.
```go title="Signature"
func (app *App) Hooks() *Hooks
```

## RebuildTree

The RebuildTree method is designed to rebuild the route tree and enable dynamic route registration. It returns a pointer to the App instance.

```go title="Signature"
func (app *App) RebuildTree() *App
```

**Note:** Use this method with caution. It is **not** thread-safe and calling it can be very performance-intensive, so it should be used sparingly and only in development mode. Avoid using it concurrently.

### Example Usage

Here’s an example of how to define and register routes dynamically:

```go
app.Get("/define", func(c Ctx) error { // Define a new route dynamically
app.Get("/dynamically-defined", func(c Ctx) error { // Adding a dynamically defined route
return c.SendStatus(http.StatusOK)
})

app.RebuildTree() // Rebuild the route tree to register the new route

return c.SendStatus(http.StatusOK)
})
```

In this example, a new route is defined and then `RebuildTree()` is called to make sure the new route is registered and available.
25 changes: 25 additions & 0 deletions docs/whats_new.md
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,31 @@ app.Route("/api").Route("/user/:id?")
});
```

### 🗺 RebuildTree

We have added a new method that allows the route tree stack to be rebuilt in runtime, with it, you can add a route while your application is running and rebuild the route tree stack to make it registered and available for calls.

You can find more reference on it in the [app](./api/app.md#rebuildtree):

#### Example Usage

```go
app.Get("/define", func(c Ctx) error { // Define a new route dynamically
app.Get("/dynamically-defined", func(c Ctx) error { // Adding a dynamically defined route
return c.SendStatus(http.StatusOK)
})

app.RebuildTree() // Rebuild the route tree to register the new route

return c.SendStatus(http.StatusOK)
})
```

In this example, a new route is defined and then `RebuildTree()` is called to make sure the new route is registered and available.

**Note:** Use this method with caution. It is **not** thread-safe and calling it can be very performance-intensive, so it should be used sparingly and only in
development mode. Avoid using it concurrently.

### 🧠 Context

### 📎 Parser
Expand Down
20 changes: 18 additions & 2 deletions router.go
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,9 @@ func (app *App) register(methods []string, pathRaw string, group *Group, handler
}

func (app *App) addRoute(method string, route *Route, isMounted ...bool) {
app.mutex.Lock()
defer app.mutex.Unlock()

// Check mounted routes
var mounted bool
if len(isMounted) > 0 {
Expand All @@ -400,15 +403,28 @@ func (app *App) addRoute(method string, route *Route, isMounted ...bool) {

// Execute onRoute hooks & change latestRoute if not adding mounted route
if !mounted {
app.mutex.Lock()
app.latestRoute = route
if err := app.hooks.executeOnRouteHooks(*route); err != nil {
panic(err)
}
app.mutex.Unlock()
}
}

// BuildTree rebuilds the prefix tree from the previously registered routes.
// This method is useful when you want to register routes dynamically after the app has started.
// It is not recommended to use this method on production environments because rebuilding
// the tree is performance-intensive and not thread-safe in runtime. Since building the tree
// is only done in the startupProcess of the app, this method does not makes sure that the
// routeTree is being safely changed, as it would add a great deal of overhead in the request.
// Latest benchmark results showed a degradation from 82.79 ns/op to 94.48 ns/op and can be found in:
// https://github.com/gofiber/fiber/issues/2769#issuecomment-2227385283
func (app *App) RebuildTree() *App {
app.mutex.Lock()
defer app.mutex.Unlock()

return app.buildTree()
}

// buildTree build the prefix tree from the previously registered routes
func (app *App) buildTree() *App {
if !app.routesRefreshed {
Expand Down
28 changes: 28 additions & 0 deletions router_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"errors"
"fmt"
"io"
"net/http"
"net/http/httptest"
"os"
"testing"
Expand Down Expand Up @@ -368,6 +369,33 @@ func Test_Router_NotFound_HTML_Inject(t *testing.T) {
require.Equal(t, "Cannot DELETE /does/not/exist<script>alert('foo');</script>", string(c.Response.Body()))
}

func Test_App_Rebuild_Tree(t *testing.T) {
t.Parallel()
app := New()

app.Get("/test", func(c Ctx) error {
app.Get("/dynamically-defined", func(c Ctx) error {
return c.SendStatus(http.StatusOK)
})

app.RebuildTree()

return c.SendStatus(http.StatusOK)
})

resp, err := app.Test(httptest.NewRequest(MethodGet, "/dynamically-defined", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, http.StatusNotFound, resp.StatusCode, "Status code")

resp, err = app.Test(httptest.NewRequest(MethodGet, "/test", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, http.StatusOK, resp.StatusCode, "Status code")

resp, err = app.Test(httptest.NewRequest(MethodGet, "/dynamically-defined", nil))
require.NoError(t, err, "app.Test(req)")
require.Equal(t, http.StatusOK, resp.StatusCode, "Status code")
}

//////////////////////////////////////////////
///////////////// BENCHMARKS /////////////////
//////////////////////////////////////////////
Expand Down