go 31 lines · 7 steps

Conditional GET with ETags in Gin

A Gin handler that fetches an article and skips resending it when the client's cached copy is still fresh.

Explained by highlit
1func GetArticle(c *gin.Context) {
2 id := c.Param("id")
3 
4 article, err := articleRepo.FindByID(c.Request.Context(), id)
5 if err != nil {
6 if errors.Is(err, sql.ErrNoRows) {
7 c.JSON(http.StatusNotFound, gin.H{"error": "article not found"})
8 return
9 }
10 c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load article"})
11 return
12 }
13 
14 etag := fmt.Sprintf(`"%x"`, sha1.Sum([]byte(fmt.Sprintf("%s-%d", article.ID, article.UpdatedAt.UnixNano()))))
15 
16 if match := c.GetHeader("If-None-Match"); match != "" {
17 for _, tag := range strings.Split(match, ",") {
18 tag = strings.TrimSpace(tag)
19 if tag == etag || tag == "*" {
20 c.Header("ETag", etag)
21 c.Header("Cache-Control", "private, must-revalidate")
22 c.Status(http.StatusNotModified)
23 return
24 }
25 }
26 }
27 
28 c.Header("ETag", etag)
29 c.Header("Cache-Control", "private, must-revalidate")
30 c.JSON(http.StatusOK, article)
31}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1ETags let a server answer with 304 Not Modified so unchanged bodies never travel the wire twice.
  2. 2Deriving the ETag from stable identity plus a modification timestamp makes it change exactly when the resource does.
  3. 3Distinguishing a not-found error from an unexpected failure lets clients react to each correctly.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Conditional GET with ETags in Gin — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code