go
46 lines · 9 steps
Per-user response caching in Gin with Redis
A Gin middleware that serves cached dashboards from Redis and captures fresh responses to populate the cache on a miss.
Explained by
highlit
1func UserDashboardCache(rdb *redis.Client, ttl time.Duration) gin.HandlerFunc {
2 return func(c *gin.Context) {
3 claims, ok := c.Get("claims")
4 if !ok {
5 c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "missing claims"})
6 return
7 }
8
9 user := claims.(*AuthClaims)
10 key := fmt.Sprintf("dashboard:v1:%s:%s", user.TenantID, user.Subject)
11
12 if cached, err := rdb.Get(c.Request.Context(), key).Bytes(); err == nil {
13 c.Header("X-Cache", "HIT")
14 c.Data(http.StatusOK, "application/json; charset=utf-8", cached)
15 c.Abort()
16 return
17 }
18
19 writer := &responseCapture{ResponseWriter: c.Writer, body: &bytes.Buffer{}}
20 c.Writer = writer
21
22 c.Next()
23
24 if writer.Status() == http.StatusOK && writer.body.Len() > 0 {
25 if err := rdb.Set(c.Request.Context(), key, writer.body.Bytes(), ttl).Err(); err != nil {
26 log.Printf("dashboard cache set failed for %s: %v", key, err)
27 }
28 }
29 writer.Header().Set("X-Cache", "MISS")
30 }
31}
32
33type responseCapture struct {
34 gin.ResponseWriter
35 body *bytes.Buffer
36}
37
38func (w *responseCapture) Write(b []byte) (int, error) {
39 w.body.Write(b)
40 return w.ResponseWriter.Write(b)
41}
42
43func (w *responseCapture) WriteString(s string) (int, error) {
44 w.body.WriteString(s)
45 return w.ResponseWriter.WriteString(s)
46}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Middleware can short-circuit a request on a cache hit and let the handler run on a miss.
- 2Wrapping gin.ResponseWriter lets you tee the response body into a buffer for later reuse.
- 3Scoping cache keys by tenant and subject keeps cached data isolated per user.
Related explainers
go
package middleware import ( "net/http"
Role-based access control middleware in Gin
middleware
authorization
closures
Intermediate
7 steps
python
from fastapi import FastAPI, Request, status from fastapi.encoders import jsonable_encoder from fastapi.exceptions import RequestValidationError from fastapi.responses import JSONResponse
Redacting sensitive fields in FastAPI errors
validation
error-handling
security
Intermediate
7 steps
rust
use std::{collections::HashSet, sync::Arc}; use axum::{ body::Body,
Feature-flag middleware in Axum
middleware
async
shared-state
Advanced
7 steps
javascript
import { unstable_cache, revalidateTag } from 'next/cache' import { db } from '@/lib/db' export const getDashboardStats = unstable_cache(
Caching dashboard stats in Next.js
caching
cache-invalidation
tag-based-revalidation
Intermediate
8 steps
go
package middleware import ( "compress/gzip"
How gzip HTTP middleware works in Go
middleware
compression
object-pooling
Intermediate
7 steps
go
package events import ( "net/http"
Two-pass JSON dispatch in Gin
polymorphic-json
request-binding
validation
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/per-user-response-caching-in-gin-with-redis-explained-go-68af/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.