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

451 lines
11 KiB
Markdown

# 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")
<div class="page-body">
<div class="container-xl">
// Your content here
</div>
</div>
}
}
```
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.