{"id":50362367,"url":"https://github.com/adrielcodeco/gorm-cache","last_synced_at":"2026-05-30T02:30:28.769Z","repository":{"id":345076711,"uuid":"1180050418","full_name":"adrielcodeco/gorm-cache","owner":"adrielcodeco","description":null,"archived":false,"fork":false,"pushed_at":"2026-03-17T14:30:12.000Z","size":32,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-03-26T10:34:40.071Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/adrielcodeco.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-12T16:42:54.000Z","updated_at":"2026-03-17T14:30:19.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/adrielcodeco/gorm-cache","commit_stats":null,"previous_names":["adrielcodeco/gorm-cache"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/adrielcodeco/gorm-cache","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adrielcodeco%2Fgorm-cache","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adrielcodeco%2Fgorm-cache/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adrielcodeco%2Fgorm-cache/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adrielcodeco%2Fgorm-cache/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/adrielcodeco","download_url":"https://codeload.github.com/adrielcodeco/gorm-cache/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/adrielcodeco%2Fgorm-cache/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33678270,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-05-30T02:30:28.175Z","updated_at":"2026-05-30T02:30:28.756Z","avatar_url":"https://github.com/adrielcodeco.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Gorm Caches\n\nGorm Caches plugin using database request reductions (easer), and response caching mechanism provide you an easy way to optimize database performance.\n\n## Features\n\n- Database request reduction. If three identical requests are running at the same time, only the first one is going to be executed, and its response will be returned for all.\n- Database response caching. By implementing the Cacher interface, you can easily setup a caching mechanism for your database queries.\n- Granular cache invalidation. Mutations automatically provide table names, entity IDs, and mutation type via `InvalidationEvent`.\n- Tag-based invalidation. Optionally tag cache entries with `TagsFunc` and selectively invalidate them with `WithInvalidateTags`.\n- Supports all databases that are supported by gorm itself.\n\n## Install\n\n```bash\ngo get -u github.com/adrielcodeco/gorm-cache/v5\n```\n\n## Usage\n\nConfigure the `easer`, and the `cacher`, and then load the plugin to gorm.\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\n\t\"github.com/adrielcodeco/gorm-cache/v5\"\n\t\"gorm.io/driver/mysql\"\n\t\"gorm.io/gorm\"\n)\n\nfunc main() {\n\tdb, _ := gorm.Open(\n\t\tmysql.Open(\"DATABASE_DSN\"),\n\t\t\u0026gorm.Config{},\n\t)\n\tcachesPlugin := \u0026caches.Caches{Conf: \u0026caches.Config{\n\t\tEaser: true,\n\t\tCacher: \u0026yourCacherImplementation{},\n\t}}\n\t_ = db.Use(cachesPlugin)\n}\n```\n\n## Easer Example\n\n```go\npackage main\n\nimport (\n\t\"fmt\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/adrielcodeco/gorm-cache/v5\"\n\t\"gorm.io/driver/mysql\"\n\t\"gorm.io/gorm\"\n)\n\ntype UserRoleModel struct {\n\tgorm.Model\n\tName string `gorm:\"unique\"`\n}\n\ntype UserModel struct {\n\tgorm.Model\n\tName   string\n\tRoleId uint\n\tRole   *UserRoleModel `gorm:\"foreignKey:role_id;references:id\"`\n}\n\nfunc main() {\n\tdb, _ := gorm.Open(\n\t\tmysql.Open(\"DATABASE_DSN\"),\n\t\t\u0026gorm.Config{},\n\t)\n\n\tcachesPlugin := \u0026caches.Caches{Conf: \u0026caches.Config{\n\t\tEaser: true,\n\t}}\n\n\t_ = db.Use(cachesPlugin)\n\n\t_ = db.AutoMigrate(\u0026UserRoleModel{})\n\n\t_ = db.AutoMigrate(\u0026UserModel{})\n\n\tadminRole := \u0026UserRoleModel{\n\t\tName: \"Admin\",\n\t}\n\tdb.FirstOrCreate(adminRole, \"Name = ?\", \"Admin\")\n\n\tguestRole := \u0026UserRoleModel{\n\t\tName: \"Guest\",\n\t}\n\tdb.FirstOrCreate(guestRole, \"Name = ?\", \"Guest\")\n\n\tdb.Save(\u0026UserModel{\n\t\tName: \"ktsivkov\",\n\t\tRole: adminRole,\n\t})\n\tdb.Save(\u0026UserModel{\n\t\tName: \"anonymous\",\n\t\tRole: guestRole,\n\t})\n\n\tvar (\n\t\tq1Users []UserModel\n\t\tq2Users []UserModel\n\t)\n\twg := \u0026sync.WaitGroup{}\n\twg.Add(2)\n\tgo func() {\n\t\tdb.Model(\u0026UserModel{}).Joins(\"Role\").Find(\u0026q1Users, \"Role.Name = ? AND Sleep(1) = false\", \"Admin\")\n\t\twg.Done()\n\t}()\n\tgo func() {\n\t\ttime.Sleep(500 * time.Millisecond)\n\t\tdb.Model(\u0026UserModel{}).Joins(\"Role\").Find(\u0026q2Users, \"Role.Name = ? AND Sleep(1) = false\", \"Admin\")\n\t\twg.Done()\n\t}()\n\twg.Wait()\n\n\tfmt.Println(fmt.Sprintf(\"%+v\", q1Users))\n\tfmt.Println(fmt.Sprintf(\"%+v\", q2Users))\n}\n```\n\n## Cacher Example (Redis)\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"time\"\n\n\t\"github.com/adrielcodeco/gorm-cache/v5\"\n\t\"github.com/redis/go-redis/v9\"\n\t\"gorm.io/driver/sqlite\"\n\t\"gorm.io/gorm\"\n)\n\ntype UserRoleModel struct {\n\tgorm.Model\n\tName string `gorm:\"unique\"`\n}\n\ntype UserModel struct {\n\tgorm.Model\n\tName   string\n\tRoleId uint\n\tRole   *UserRoleModel `gorm:\"foreignKey:role_id;references:id\"`\n}\n\ntype redisCacher struct {\n\trdb *redis.Client\n}\n\nfunc (c *redisCacher) Get(ctx context.Context, key string, q *caches.Query[any]) (*caches.Query[any], error) {\n\tres, err := c.rdb.Get(ctx, key).Result()\n\tif err == redis.Nil {\n\t\treturn nil, nil\n\t}\n\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tif err := q.Unmarshal([]byte(res)); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn q, nil\n}\n\nfunc (c *redisCacher) Store(ctx context.Context, key string, val *caches.Query[any]) error {\n\tres, err := val.Marshal()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\t// You can use caches.TagsFromContext(ctx) here to store tags alongside the cache entry\n\tc.rdb.Set(ctx, key, res, 300*time.Second)\n\treturn nil\n}\n\nfunc (c *redisCacher) Invalidate(ctx context.Context, event *caches.InvalidationEvent) error {\n\t// Use event.Tables, event.EntityIDs, event.MutationType for granular invalidation\n\t// Use event.Tags for tag-based invalidation (if WithInvalidateTags was used)\n\t// Fallback: invalidate all if no tags are present\n\tvar (\n\t\tcursor uint64\n\t\tkeys   []string\n\t)\n\tfor {\n\t\tvar (\n\t\t\tk   []string\n\t\t\terr error\n\t\t)\n\t\tk, cursor, err = c.rdb.Scan(ctx, cursor, fmt.Sprintf(\"%s*\", caches.IdentifierPrefix), 0).Result()\n\t\tif err != nil {\n\t\t\treturn err\n\t\t}\n\t\tkeys = append(keys, k...)\n\t\tif cursor == 0 {\n\t\t\tbreak\n\t\t}\n\t}\n\n\tif len(keys) \u003e 0 {\n\t\tif _, err := c.rdb.Del(ctx, keys...).Result(); err != nil {\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n\nfunc main() {\n\tdb, _ := gorm.Open(sqlite.Open(\"gorm.db\"), \u0026gorm.Config{\n\t\tAllowGlobalUpdate: true,\n\t})\n\n\tcachesPlugin := \u0026caches.Caches{Conf: \u0026caches.Config{\n\t\tCacher: \u0026redisCacher{\n\t\t\trdb: redis.NewClient(\u0026redis.Options{\n\t\t\t\tAddr:     \"localhost:6379\",\n\t\t\t\tPassword: \"\",\n\t\t\t\tDB:       0,\n\t\t\t}),\n\t\t},\n\t}}\n\n\t_ = db.Use(cachesPlugin)\n\n\t_ = db.AutoMigrate(\u0026UserRoleModel{})\n\t_ = db.AutoMigrate(\u0026UserModel{})\n\n\tdb.Delete(\u0026UserRoleModel{})\n\tdb.Delete(\u0026UserModel{})\n\n\tadminRole := \u0026UserRoleModel{\n\t\tName: \"Admin\",\n\t}\n\tdb.Save(adminRole)\n\n\tguestRole := \u0026UserRoleModel{\n\t\tName: \"Guest\",\n\t}\n\tdb.Save(guestRole)\n\n\tdb.Save(\u0026UserModel{\n\t\tName: \"ktsivkov\",\n\t\tRole: adminRole,\n\t})\n\n\tdb.Save(\u0026UserModel{\n\t\tName: \"anonymous\",\n\t\tRole: guestRole,\n\t})\n\n\tq1User := \u0026UserModel{}\n\tdb.WithContext(context.Background()).Find(q1User, \"Name = ?\", \"ktsivkov\")\n\tq2User := \u0026UserModel{}\n\tdb.WithContext(context.Background()).Find(q2User, \"Name = ?\", \"ktsivkov\")\n\n\tfmt.Println(fmt.Sprintf(\"%+v\", q1User))\n\tfmt.Println(fmt.Sprintf(\"%+v\", q2User))\n}\n```\n\n## Cacher Example (Memory)\n\n```go\npackage main\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"sync\"\n\n\t\"github.com/adrielcodeco/gorm-cache/v5\"\n\t\"gorm.io/driver/sqlite\"\n\t\"gorm.io/gorm\"\n)\n\ntype UserRoleModel struct {\n\tgorm.Model\n\tName string `gorm:\"unique\"`\n}\n\ntype UserModel struct {\n\tgorm.Model\n\tName   string\n\tRoleId uint\n\tRole   *UserRoleModel `gorm:\"foreignKey:role_id;references:id\"`\n}\n\ntype memoryCacher struct {\n\tstore *sync.Map\n}\n\nfunc (c *memoryCacher) init() {\n\tif c.store == nil {\n\t\tc.store = \u0026sync.Map{}\n\t}\n}\n\nfunc (c *memoryCacher) Get(ctx context.Context, key string, q *caches.Query[any]) (*caches.Query[any], error) {\n\tc.init()\n\tval, ok := c.store.Load(key)\n\tif !ok {\n\t\treturn nil, nil\n\t}\n\n\tif err := q.Unmarshal(val.([]byte)); err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn q, nil\n}\n\nfunc (c *memoryCacher) Store(ctx context.Context, key string, val *caches.Query[any]) error {\n\tc.init()\n\tres, err := val.Marshal()\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tc.store.Store(key, res)\n\treturn nil\n}\n\nfunc (c *memoryCacher) Invalidate(ctx context.Context, event *caches.InvalidationEvent) error {\n\tc.store = \u0026sync.Map{}\n\treturn nil\n}\n\nfunc main() {\n\tdb, _ := gorm.Open(sqlite.Open(\"gorm.db\"), \u0026gorm.Config{\n\t\tAllowGlobalUpdate: true,\n\t})\n\n\tcachesPlugin := \u0026caches.Caches{Conf: \u0026caches.Config{\n\t\tCacher: \u0026memoryCacher{},\n\t}}\n\n\t_ = db.Use(cachesPlugin)\n\n\t_ = db.AutoMigrate(\u0026UserRoleModel{})\n\t_ = db.AutoMigrate(\u0026UserModel{})\n\n\tdb.Delete(\u0026UserRoleModel{})\n\tdb.Delete(\u0026UserModel{})\n\n\tadminRole := \u0026UserRoleModel{\n\t\tName: \"Admin\",\n\t}\n\tdb.Save(adminRole)\n\n\tguestRole := \u0026UserRoleModel{\n\t\tName: \"Guest\",\n\t}\n\tdb.Save(guestRole)\n\n\tdb.Save(\u0026UserModel{\n\t\tName: \"ktsivkov\",\n\t\tRole: adminRole,\n\t})\n\n\tdb.Save(\u0026UserModel{\n\t\tName: \"anonymous\",\n\t\tRole: guestRole,\n\t})\n\n\tq1User := \u0026UserModel{}\n\tdb.WithContext(context.Background()).Find(q1User, \"Name = ?\", \"ktsivkov\")\n\tq2User := \u0026UserModel{}\n\tdb.WithContext(context.Background()).Find(q2User, \"Name = ?\", \"ktsivkov\")\n\n\tfmt.Println(fmt.Sprintf(\"%+v\", q1User))\n\tfmt.Println(fmt.Sprintf(\"%+v\", q2User))\n}\n```\n\n## Tags (Query Keys)\n\nTags allow you to selectively invalidate cache entries, similar to TanStack React Query's query keys. Instead of invalidating all cache entries on every mutation, you can tag cached queries and only invalidate the relevant ones.\n\n### Setup TagsFunc\n\nUse `Config.TagsFunc` to generate tags for each cached query. Tags are passed to your `Cacher.Store` implementation via context (retrievable with `caches.WithTags`):\n\n```go\ncachesPlugin := \u0026caches.Caches{Conf: \u0026caches.Config{\n\tCacher: \u0026yourCacher{},\n\tTagsFunc: func(db *gorm.DB) []string {\n\t\treturn []string{db.Statement.Table}\n\t},\n}}\n```\n\n### Invalidate by Tags\n\nWhen performing mutations, use `caches.WithInvalidateTags` to specify which tags to invalidate:\n\n```go\nctx := caches.WithInvalidateTags(context.Background(), \"users\")\ndb.WithContext(ctx).Create(\u0026User{Name: \"John\"})\n```\n\nIn your `Cacher.Invalidate` implementation, check `event.Tags` to determine which entries to invalidate:\n\n```go\nfunc (c *yourCacher) Invalidate(ctx context.Context, event *caches.InvalidationEvent) error {\n\tif len(event.Tags) \u003e 0 {\n\t\t// Selectively invalidate entries matching these tags\n\t\treturn c.invalidateByTags(ctx, event.Tags)\n\t}\n\t// Fallback: invalidate all (no tags specified)\n\treturn c.invalidateAll(ctx)\n}\n```\n\n### InvalidationEvent\n\nEvery mutation callback receives an `InvalidationEvent` with:\n\n| Field | Type | Description |\n|-------|------|-------------|\n| `Tables` | `[]string` | Tables involved in the mutation (main table + relationships) |\n| `EntityIDs` | `[]interface{}` | Primary key values of affected entities |\n| `MutationType` | `MutationType` | `MutationCreate`, `MutationUpdate`, or `MutationDelete` |\n| `Tags` | `[]string` | Tags from `WithInvalidateTags` context (empty if not set) |\n\n## License\n\nMIT license.\n\n## Easer\nThe easer is an adjusted version of the [ServantGo](https://github.com/ktsivkov/servantgo) library to fit the needs of this plugin.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadrielcodeco%2Fgorm-cache","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fadrielcodeco%2Fgorm-cache","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fadrielcodeco%2Fgorm-cache/lists"}