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
Install development tools (optional but recommended)
# 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
- Login/Registration: Test user authentication flows
- Dashboard: Verify fuel stops display correctly
- Add Fuel Stop: Test form submission and validation
- Vehicle Management: Test vehicle CRUD operations
- Settings: Test user preference updates
- 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
-
Start development server:
make run-dev # or air -
Make changes to templ files:
- Edit
.templfiles ininternal/views/ - Templates are automatically regenerated in watch mode
- Edit
-
Format code regularly:
make format
Adding New Pages
-
Create templ template:
# Create new template file touch internal/views/pages/my_new_page.templ -
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> } } -
Generate and create handler:
make generate -
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) } -
Add route:
r.HandleFunc("/my-new-page", templHandlers.MyNewPageHandler).Methods("GET")
Troubleshooting
Common Issues
-
Templates not updating:
- Make sure you're running
make generateafter changing.templfiles - Check that generated
*_templ.gofiles are being updated
- Make sure you're running
-
Import errors:
- Ensure all imports in templ files are valid Go imports
- Check that models and services are properly imported
-
Context issues:
- Make sure user is properly set in request context
- Verify auth middleware is working correctly
-
Static files not loading:
- Check that static file serving is properly configured
- Verify file paths in templates are correct
Performance Optimization
-
Enable compression:
// Add gzip middleware import "github.com/gorilla/handlers" router := setupRoutes() handler := handlers.CompressHandler(router) -
Add caching headers:
// For static files r.PathPrefix("/static/").Handler( http.StripPrefix("/static/", addCacheHeaders(http.FileServer(http.Dir("./static/"))))) -
Monitor memory usage:
# Check memory usage go tool pprof http://localhost:8080/debug/pprof/heap
Best Practices
- Component Reusability: Break down complex pages into smaller, reusable components
- Type Safety: Always use strongly-typed parameters in templ functions
- Error Handling: Properly handle rendering errors in handlers
- Performance: Use templ's compile-time optimization features
- Testing: Write tests for your handlers and components
- Documentation: Document complex templ components and their usage
Next Steps
After successful migration:
- Add Tests: Create tests for your new handlers and components
- Performance Monitoring: Set up monitoring for template rendering performance
- Accessibility: Ensure templates meet accessibility standards
- SEO: Add proper meta tags and structured data
- Internationalization: Consider adding i18n support
Support
For issues or questions:
- Check the templ documentation
- Review the example handlers in
internal/handlers/templ_handlers.go - Look at the component examples in
internal/views/components/ - 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.