#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.