Files
tankstopp-app/docs/INTEGRATION_GUIDE.md
2025-07-07 01:44:12 +02:00

11 KiB

TankStopp Templ Integration Guide

Overview

This guide walks you through the process of migrating from traditional HTML templates to the new a-h/templ system in TankStopp. The migration provides better performance, type safety, and developer experience.

Prerequisites

Before starting the migration, ensure you have:

  • Go 1.21 or later installed
  • Access to the TankStopp codebase
  • Basic understanding of Go templates and HTML
  • Familiarity with the existing codebase structure

Step 1: Install Required Tools

Install templ CLI

go install github.com/a-h/templ/cmd/templ@latest
# Hot reload tool
go install github.com/cosmtrek/air@latest

# File watcher (for manual watch scripts)
# macOS
brew install entr

# Linux
sudo apt-get install entr

Step 2: Update Dependencies

The templ dependency should already be in your go.mod file:

require (
    github.com/a-h/templ v0.3.906
    // ... other dependencies
)

If not, add it:

go get github.com/a-h/templ@latest
go mod tidy

Step 3: Generate Templ Templates

Generate the Go code from the templ templates:

# Using the build script
./scripts/build.sh generate

# Or using templ directly
templ generate ./internal/views/

# Or using the Makefile
make generate

Step 4: Update Your Handlers

Before (Old HTML Template System)

// Old handler using html/template
func DashboardHandler(w http.ResponseWriter, r *http.Request) {
    data := struct {
        Title string
        User  *models.User
        Stops []models.FuelStop
    }{
        Title: "Dashboard",
        User:  user,
        Stops: stops,
    }
    
    tmpl := template.Must(template.ParseFiles("templates/dashboard.html"))
    tmpl.Execute(w, data)
}

After (New Templ System)

// New handler using templ
func DashboardHandler(w http.ResponseWriter, r *http.Request) {
    // Get data...
    user := getUserFromContext(r.Context())
    stops, _ := h.db.GetFuelStops(user.ID, "", "", "", "")
    vehicles, _ := h.db.GetVehicles(user.ID)
    
    // Render templ component
    component := pages.DashboardPage(user, user.Username, stops, vehicles, len(stops), 0.0, 0.0, nil)
    
    w.Header().Set("Content-Type", "text/html")
    if err := component.Render(r.Context(), w); err != nil {
        http.Error(w, "Failed to render dashboard", http.StatusInternalServerError)
        return
    }
}

Step 5: Update Route Registration

Replace Old Template Handlers

Update your main router to use the new templ handlers:

// main.go or router setup
func setupRoutes() *mux.Router {
    r := mux.NewRouter()
    
    // Create templ handlers
    templHandlers := handlers.NewTemplHandlers(db, auth, currency)
    
    // Authentication routes
    r.HandleFunc("/login", templHandlers.LoginHandler).Methods("GET", "POST")
    r.HandleFunc("/register", templHandlers.RegisterHandler).Methods("GET", "POST")
    r.HandleFunc("/logout", templHandlers.LogoutHandler).Methods("POST")
    
    // Protected routes (add auth middleware)
    protected := r.PathPrefix("/").Subrouter()
    protected.Use(authMiddleware)
    
    protected.HandleFunc("/dashboard", templHandlers.DashboardHandler).Methods("GET")
    protected.HandleFunc("/add", templHandlers.AddFuelStopHandler).Methods("GET", "POST")
    protected.HandleFunc("/edit/{id}", templHandlers.EditFuelStopHandler).Methods("GET", "POST")
    protected.HandleFunc("/vehicles", templHandlers.VehiclesHandler).Methods("GET")
    protected.HandleFunc("/add-vehicle", templHandlers.AddVehicleHandler).Methods("GET", "POST")
    protected.HandleFunc("/settings", templHandlers.SettingsHandler).Methods("GET")
    
    // Static files
    r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static/"))))
    
    return r
}

Step 6: Update Authentication Middleware

Ensure your auth middleware sets the user in the request context:

func authMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        // Get session from cookie
        cookie, err := r.Cookie("session")
        if err != nil {
            http.Redirect(w, r, "/login", http.StatusSeeOther)
            return
        }
        
        // Validate session and get user
        user, err := auth.ValidateSession(cookie.Value)
        if err != nil {
            http.Redirect(w, r, "/login", http.StatusSeeOther)
            return
        }
        
        // Add user to request context
        ctx := context.WithValue(r.Context(), "user", user)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

Step 7: Update Main Application

Update your main application file to use the new routing:

// main.go
func main() {
    // Initialize database, auth, currency services
    db := database.New("fuel_stops.db")
    authService := auth.New(db)
    currencyService := currency.New()
    
    // Setup routes with new handlers
    router := setupRoutes(db, authService, currencyService)
    
    // Start server
    log.Println("Server starting on :8080")
    log.Fatal(http.ListenAndServe(":8080", router))
}

Step 8: Build and Test

Build the application

# Using the build script
./scripts/build.sh dev

# Or using the Makefile
make dev

# Or manual build
make generate
go build -o tankstopp ./cmd/main.go

Run the application

# Direct run
./tankstopp

# Or with hot reload
make run-dev

# Or using air
air

Test the migration

  1. Login/Registration: Test user authentication flows
  2. Dashboard: Verify fuel stops display correctly
  3. Add Fuel Stop: Test form submission and validation
  4. Vehicle Management: Test vehicle CRUD operations
  5. Settings: Test user preference updates
  6. Responsive Design: Test on different screen sizes

Step 9: Clean Up Old Templates

Once everything is working, remove the old HTML templates:

# Backup old templates (optional)
mkdir -p backup
mv templates backup/

# Or remove directly
rm -rf templates/

Step 10: Update Build Process

Update CI/CD Pipeline

If you have a CI/CD pipeline, update it to include templ generation:

# .github/workflows/build.yml
- name: Generate templ templates
  run: |
    go install github.com/a-h/templ/cmd/templ@latest
    templ generate ./internal/views/

- name: Build application
  run: go build -o tankstopp ./cmd/main.go

Update Docker Build

If using Docker, update your Dockerfile:

# Dockerfile
FROM golang:1.21-alpine AS builder

WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download

# Install templ
RUN go install github.com/a-h/templ/cmd/templ@latest

COPY . .

# Generate templates and build
RUN templ generate ./internal/views/
RUN go build -o tankstopp ./cmd/main.go

FROM alpine:latest
RUN apk --no-cache add ca-certificates
WORKDIR /root/
COPY --from=builder /app/tankstopp .
COPY --from=builder /app/static ./static
CMD ["./tankstopp"]

Development Workflow

Daily Development

  1. Start development server:

    make run-dev
    # or
    air
    
  2. Make changes to templ files:

    • Edit .templ files in internal/views/
    • Templates are automatically regenerated in watch mode
  3. Format code regularly:

    make format
    

Adding New Pages

  1. Create templ template:

    # Create new template file
    touch internal/views/pages/my_new_page.templ
    
  2. Define the template:

    package pages
    
    import (
        "tankstopp/internal/views/components"
        "tankstopp/internal/models"
    )
    
    templ MyNewPage(user *models.User, data MyData) {
        @components.BaseLayout("My New Page", user, user.Username) {
            @components.PageHeader("Subtitle", "My New Page")
    
            <div class="page-body">
                <div class="container-xl">
                    // Your content here
                </div>
            </div>
        }
    }
    
  3. Generate and create handler:

    make generate
    
  4. Add handler method:

    func (h *TemplHandlers) MyNewPageHandler(w http.ResponseWriter, r *http.Request) {
        user := getUserFromContext(r.Context())
        // Get data...
    
        component := pages.MyNewPage(user, data)
    
        w.Header().Set("Content-Type", "text/html")
        component.Render(r.Context(), w)
    }
    
  5. Add route:

    r.HandleFunc("/my-new-page", templHandlers.MyNewPageHandler).Methods("GET")
    

Troubleshooting

Common Issues

  1. Templates not updating:

    • Make sure you're running make generate after changing .templ files
    • Check that generated *_templ.go files are being updated
  2. Import errors:

    • Ensure all imports in templ files are valid Go imports
    • Check that models and services are properly imported
  3. Context issues:

    • Make sure user is properly set in request context
    • Verify auth middleware is working correctly
  4. Static files not loading:

    • Check that static file serving is properly configured
    • Verify file paths in templates are correct

Performance Optimization

  1. Enable compression:

    // Add gzip middleware
    import "github.com/gorilla/handlers"
    
    router := setupRoutes()
    handler := handlers.CompressHandler(router)
    
  2. Add caching headers:

    // For static files
    r.PathPrefix("/static/").Handler(
        http.StripPrefix("/static/", 
            addCacheHeaders(http.FileServer(http.Dir("./static/")))))
    
  3. Monitor memory usage:

    # Check memory usage
    go tool pprof http://localhost:8080/debug/pprof/heap
    

Best Practices

  1. Component Reusability: Break down complex pages into smaller, reusable components
  2. Type Safety: Always use strongly-typed parameters in templ functions
  3. Error Handling: Properly handle rendering errors in handlers
  4. Performance: Use templ's compile-time optimization features
  5. Testing: Write tests for your handlers and components
  6. Documentation: Document complex templ components and their usage

Next Steps

After successful migration:

  1. Add Tests: Create tests for your new handlers and components
  2. Performance Monitoring: Set up monitoring for template rendering performance
  3. Accessibility: Ensure templates meet accessibility standards
  4. SEO: Add proper meta tags and structured data
  5. Internationalization: Consider adding i18n support

Support

For issues or questions:

  1. Check the templ documentation
  2. Review the example handlers in internal/handlers/templ_handlers.go
  3. Look at the component examples in internal/views/components/
  4. Check the build scripts and Makefile for automation examples

Conclusion

The migration to templ provides significant benefits:

  • Type Safety: Compile-time checking prevents runtime errors
  • Performance: No runtime template parsing
  • Developer Experience: Better IDE support and refactoring
  • Maintainability: Component-based architecture

The new system is more robust and provides a better foundation for future development.