# 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 ```bash go install github.com/a-h/templ/cmd/templ@latest ``` ### Install development tools (optional but recommended) ```bash # 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: ```go require ( github.com/a-h/templ v0.3.906 // ... other dependencies ) ``` If not, add it: ```bash go get github.com/a-h/templ@latest go mod tidy ``` ## Step 3: Generate Templ Templates Generate the Go code from the templ templates: ```bash # 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) ```go // 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) ```go // 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: ```go // 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: ```go 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: ```go // 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 ```bash # 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 ```bash # 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: ```bash # 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: ```yaml # .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 # 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**: ```bash 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**: ```bash make format ``` ### Adding New Pages 1. **Create templ template**: ```bash # Create new template file touch internal/views/pages/my_new_page.templ ``` 2. **Define the template**: ```go 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")
// Your content here
} } ``` 3. **Generate and create handler**: ```bash make generate ``` 4. **Add handler method**: ```go 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**: ```go 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**: ```go // Add gzip middleware import "github.com/gorilla/handlers" router := setupRoutes() handler := handlers.CompressHandler(router) ``` 2. **Add caching headers**: ```go // For static files r.PathPrefix("/static/").Handler( http.StripPrefix("/static/", addCacheHeaders(http.FileServer(http.Dir("./static/"))))) ``` 3. **Monitor memory usage**: ```bash # 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](https://templ.guide/) 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.