# Echo v5 Migration Plan ## Overview This document outlines the steps required to migrate from Echo v4 to Echo v5, including API changes, type signature updates, and middleware modifications. ## Errors Identified ### 1. Deprecated Middleware **Error:** `echomiddleware.Logger undefined` **Solution:** Replace `echomiddleware.Logger()` with `echomiddleware.RequestLogger()` **Files affected:** - `cmd/server/main.go` (line 142) - `internal/router/router.go` (line 144) ### 2. Removed API Methods **Error:** `a.echo.Close undefined (type *echo.Echo has no field or method Close)` **Solution:** Remove the `echo.Close()` call as v5 uses different graceful shutdown mechanism **Files affected:** - `internal/app/app.go` (line 86) ### 3. Type Signature & Response API Changes **Error:** Multiple type mismatches and Response API changes in Echo v5 **Root Cause:** Echo v5 uses `*echo.Context` (pointer) for handlers but some code uses `echo.Context` (value). Response wrapper API also changed. **Solution:** - Update all middleware to use `*echo.Context` - Update helper functions to accept `*echo.Context` - Fix Response wrapper usage for Echo v5 API **Files affected:** - `internal/middleware/device_auth.go` (lines 38, 170, 212) - `internal/middleware/error_handler.go` (lines 44, 69, 82, 84, 87, 89) - `internal/middleware/rate_limiter.go` (line 102) - `internal/middleware/request_tracing.go` (lines 48, 58, 60) - `internal/middleware/security.go` (line 14) --- ## Step-by-Step Fixes ### Step 1: Fix Deprecated Logger Middleware #### File: `cmd/server/main.go` **Line 142:** ```go // BEFORE: e.Use(echomiddleware.Logger()) // AFTER: e.Use(echomiddleware.RequestLogger()) ``` #### File: `internal/router/router.go` **Line ~144:** ```go // BEFORE: e.Use(echomiddleware.Logger()) // AFTER: e.Use(echomiddleware.RequestLogger()) ``` --- ### Step 2: Fix Removed API Methods #### File: `internal/app/app.go` **Lines 26-32 - Initialize server field:** ```go // REPLACE lines 26-32 with: func New(echo *echo.Echo) *App { return &App{ echo: echo, server: nil, // Will be set in StartServer() shutdownTimeout: 30 * time.Second, shutdownDone: make(chan struct{}), } } ``` **Add StartServer method after New() (after line 32):** ```go // StartServer creates HTTP server and starts listening func (a *App) StartServer(addr string) error { a.server = &http.Server{ Addr: addr, Handler: a.echo, } // Start HTTP server in background go func() { if err := a.server.ListenAndServe(); err != nil && err != http.ErrServerClosed { log.Fatalf("Server failed to start: %v", err) } }() return nil } ``` **Lines 82-94 - Replace goroutine in Shutdown():** ```go // REPLACE lines 82-94 with: // Stop accepting new connections and shutdown HTTP server log.Println("Stopping HTTP server...") if a.server != nil { ctx, cancel := context.WithTimeout(context.Background(), a.shutdownTimeout) defer cancel() if err := a.server.Shutdown(ctx); err != nil { log.Printf("Error stopping HTTP server: %v", err) } } log.Println("All services stopped") ``` #### File: `cmd/server/main.go` **Lines 197-214 - Replace server startup:** ```go // REPLACE lines 197-214 with: // ======================================================================== // START SERVER (managed by app lifecycle) // ======================================================================== log.Printf("Starting server on port %s", cfg.ServerPort) // Start HTTP server if err := application.StartServer(":" + cfg.ServerPort); err != nil { log.Fatalf("Failed to start server: %v", err) } // Start application lifecycle (blocks until shutdown signal) if err := application.Start(); err != nil { log.Fatalf("Application error: %v", err) } ``` --- ### Step 3: Fix Middleware Type Signatures #### Pattern for Echo v5 Middleware **Echo v5 Middleware Pattern:** ```go func MiddlewareFunc(next echo.HandlerFunc) echo.HandlerFunc { return func(c *echo.Context) error { // ← POINTER (required in v5) // ... middleware logic return next(c) // ← c is already a pointer, no & needed } } ``` #### File: `internal/middleware/device_auth.go` **Line 38:** ```go // BEFORE: return func(c echo.Context) error { // AFTER: return func(c *echo.Context) error { ``` **Line 170:** ```go // BEFORE: return func(c echo.Context) error { // AFTER: return func(c *echo.Context) error { ``` **Line 212:** ```go // BEFORE: return func(c echo.Context) error { // AFTER: return func(c *echo.Context) error { ``` #### File: `internal/middleware/error_handler.go` **Line 44:** ```go // BEFORE: func RespondWithError(c echo.Context, code int, message string, err error) error { // AFTER: func RespondWithError(c *echo.Context, code int, message string, err error) error { ``` **Line 69:** ```go // BEFORE: func RespondWithHTTPError(c echo.Context, httpErr *HTTPError) error { // AFTER: func RespondWithHTTPError(c *echo.Context, httpErr *HTTPError) error { ``` **Line 82:** ```go // BEFORE: func WrapHandler(fn func(c echo.Context) error) echo.HandlerFunc { return func(c *echo.Context) error { err := fn(c) // AFTER: func WrapHandler(fn func(*echo.Context) error) echo.HandlerFunc { return func(c *echo.Context) error { err := fn(c) // c is already *echo.Context ``` **Lines 84, 87, 89:** No change - calls to helpers will now work with updated signatures #### File: `internal/middleware/rate_limiter.go` **Line 102:** ```go // BEFORE: return func(c echo.Context) error { // AFTER: return func(c *echo.Context) error { ``` #### File: `internal/middleware/request_tracing.go` **Line 48:** ```go // BEFORE: return func(c echo.Context) error { // AFTER: return func(c *echo.Context) error { ``` **Lines 57-61:** ```go // BEFORE: recorder := &responseWriter{ ResponseWriter: c.Response().Writer, } c.Response().Writer = recorder // AFTER: recorder := &responseWriter{ ResponseWriter: *c.Response(), // Dereference: *echo.Response -> http.ResponseWriter } ``` **Note:** `c.Response().Status` (line 108) should work - Status field exists in Echo v5 #### File: `internal/middleware/security.go` **Line 14:** ```go // BEFORE: return func(c echo.Context) error { // AFTER: return func(c *echo.Context) error { ``` --- ## WebSocket Fix After Echo v5 migration, the WebSocket handler should now work natively: ### File: `internal/handlers/websocket.go` The existing code should now work: ```go ws, err := upgrader.Upgrade(c.Response(), c.Request(), nil) ``` This previously failed with "response does not implement http.Hijacker" but Echo v5's Response now supports the `rwUnwrapper` interface needed for WebSocket upgrades. --- ## Verification After making all changes: 1. **Build the project:** ```bash go build ./cmd/server ``` 2. **Run tests:** ```bash go test ./cmd/server/tests/... -v ``` 3. **Test WebSocket manually:** - Get JWT token: `curl -X POST http://localhost:8765/api/auth/login -H "Content-Type: application/json" -d '{"login":"testuser@tests.bookhoard.internal","password":"Test@Pass123!"}' | jq -r '.access_token'` - Connect in browser console: `new WebSocket('ws://localhost:8765/ws/sync?token=YOUR_TOKEN')` --- ## Rollback Plan If Echo v5 migration fails: 1. Revert go.mod changes: ```go // Change back to v4: github.com/labstack/echo/v4 v4.15.1 github.com/labstack/echo-jwt/v4 v4.4.0 ``` 2. Restore import statements in all modified files 3. Revert all code changes --- ## References - [Echo v5 Changelog](https://github.com/labstack/echo/blob/master/CHANGELOG.md) - [Echo v5 Middleware Documentation](https://echo.labstack.com/docs/middleware) - [Echo WebSocket Cookbook](https://echo.labstack.com/docs/cookbook/websocket)