#Go#HTML#TailwindCSS

Pizza Tracker

February 23rd, 2025

I built this project in an hour ... and it shows. Despite the low amount of effort I put into this project, it's been really popular amongst my friend groups, and I love it for all its eccentricity.

The pizzas at Thrifty Foods would occasionally go on sale, often down to $3.49! So, instead of having to check their website for when it was on sale, I built out a small site that does this for me.

Pizza Schema ​

To start, I needed some way to keep track of the different types of pizza, their prices, and their images. Structs were perfect for this:

go

const (
	PizzaURL = "https://www.thriftyfoods.com/product/pizzaristorante-thin-crust-spinach/00000_000000005833617000"
)

type Pizza struct {
	Name     string
	Price    float64
	ImageURL string
}

type Status struct {
	OnSale bool
	Price  float64
}

From there, we just need to fetch the information using the PizzaURL, parse the data, then display it!

Fetching & Parsing ​

To populate our structs with pizza data, we scrape the pizza link and grab the prices, images, and name from the page.

go
// Parse and create an array of Pizza structs
func getPizzas(url string) []Pizza {
	var pizzas []Pizza

	c := colly.NewCollector()
	c.OnHTML("div.grid", func(e *colly.HTMLElement) {

		imgURL := e.ChildAttr("img", "src")
		title := e.ChildText("a.js-ga-productname")
		price := e.ChildText("span.price")

		if imgURL == "" || title == "" || price == "" {
			return
		}

		cleanedPrice := strings.Replace(price, "$", "", -1)
		p, err := strconv.ParseFloat(cleanedPrice, 64)

		if err != nil {
			return
		}

		pizza := Pizza{
			Name:     title,
			Price:    p,
			ImageURL: imgURL,
		}
		pizzas = append(pizzas, pizza)
	})

	err := c.Visit(url)

	if err != nil {
		fmt.Println("Error visiting page:", err)
	}

	return pizzas
}

We now have a complete set of pizzas to display!

Output via Templates ​

To determine if we should display if the pizzas are on sale or not, we check if the price is lower than $5. If it is, then we display the crazy styled page, and if not, just the boring page.

go
func main() {
	r := gin.Default()
	r.LoadHTMLGlob("templates/*")

	r.GET("/", func(c *gin.Context) {
		pizzas := getPizzas(PizzaURL)
		onSale := false

		if len(pizzas) > 0 && pizzas[0].Price < 5 {
			onSale = true
		}

		c.HTML(200, "index.html", gin.H{
			"pizzas": pizzas,
			"status": Status{
				OnSale: onSale,
				Price: pizzas[0].Price
		}})
	})

	r.Run()
}

Templating ​

Here's the fun part! I wanted to get this done quickly, so I imported TailwindCSS from their CDN. I used HTML templates to conditionally render / iterate over all the pizzas.