Build a Gin‑Based CRUD API with MongoDB and Redis Caching in Go

This guide demonstrates how to create a simple Go web API using the Gin framework, define a recipe data model, implement CRUD endpoints backed by MongoDB, add Redis caching for list queries, and provide a complete runnable example with testing scripts.

MaGe Linux Operations
MaGe Linux Operations
MaGe Linux Operations
Build a Gin‑Based CRUD API with MongoDB and Redis Caching in Go

Introduction

This article shows how to build a Web API with the Gin framework in Go, providing basic CRUD functionality, storing data in MongoDB, and using Redis as a cache layer.

Define Data Model

package models

import (
    "time"
    "go.mongodb.org/mongo-driver/bson/primitive"
)

type Recipe struct {
    ID           primitive.ObjectID `json:"id" bson:"_id"`
    Name         string             `json:"name" bson:"name"`
    Tags         []string           `json:"tags" bson:"tags"`
    Ingredients  []string           `json:"ingredients" bson:"ingredients"`
    Instructions []string           `json:"instructions" bson:"instructions"`
    PublishedAt  time.Time          `json:"publishedAt" bson:"publishedAt"`
}

Design API

Endpoints:

GET /recipes – return a list of recipes

POST /recipes – create a new recipe

PUT /recipes/{id} – update an existing recipe

DELETE /recipes/{id} – delete a recipe

GET /recipes/search?tag=X – query recipes by tag

Implement Handlers

package handlers

import (
    "context"
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/go-redis/redis"
    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/bson/primitive"
    "go.mongodb.org/mongo-driver/mongo"

    "buildginapp/models"
)

type RecipesHandler struct {
    collection  *mongo.Collection
    ctx         context.Context
    redisClient *redis.Client
}

func NewRecipesHandler(ctx context.Context, collection *mongo.Collection, redisClient *redis.Client) *RecipesHandler {
    return &RecipesHandler{collection: collection, ctx: ctx, redisClient: redisClient}
}

// GET /recipes
func (handler *RecipesHandler) ListRecipesHandler(c *gin.Context) {
    val, err := handler.redisClient.Get("recipes").Result()
    if err == redis.Nil {
        log.Println("Request to MongoDB")
        cur, err := handler.collection.Find(handler.ctx, bson.M{})
        if err != nil {
            c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
            return
        }
        defer cur.Close(handler.ctx)
        var recipes []models.Recipe
        for cur.Next(handler.ctx) {
            var recipe models.Recipe
            cur.Decode(&recipe)
            recipes = append(recipes, recipe)
        }
        data, _ := json.Marshal(recipes)
        handler.redisClient.Set("recipes", string(data), 3600*time.Second)
        c.JSON(http.StatusOK, recipes)
    } else if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
    } else {
        log.Println("Request to redis")
        var recipes []models.Recipe
        json.Unmarshal([]byte(val), &recipes)
        c.JSON(http.StatusOK, recipes)
    }
}

// POST /recipes
func (handler *RecipesHandler) NewRecipeHandler(c *gin.Context) {
    var recipe models.Recipe
    if err := c.ShouldBindJSON(&recipe); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    recipe.ID = primitive.NewObjectID()
    recipe.PublishedAt = time.Now()
    _, err := handler.collection.InsertOne(handler.ctx, recipe)
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "Error while inserting a new recipe"})
        return
    }
    handler.redisClient.Del("recipes")
    c.JSON(http.StatusOK, recipe)
}

// PUT /recipes/:id
func (handler *RecipesHandler) UpdateRecipeHandler(c *gin.Context) {
    id := c.Param("id")
    var recipe models.Recipe
    if err := c.ShouldBindJSON(&recipe); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }
    objectId, _ := primitive.ObjectIDFromHex(id)
    _, err := handler.collection.UpdateOne(handler.ctx, bson.M{"_id": objectId}, bson.D{{"$set", bson.D{{"name", recipe.Name}, {"instructions", recipe.Instructions}, {"ingredients", recipe.Ingredients}, {"tags", recipe.Tags}}}})
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    c.JSON(http.StatusOK, gin.H{"message": "update success"})
}

// DELETE /recipes/:id
func (handler *RecipesHandler) DeleteRecipeHandler(c *gin.Context) {
    id := c.Param("id")
    objectId, _ := primitive.ObjectIDFromHex(id)
    _, err := handler.collection.DeleteOne(handler.ctx, bson.M{"_id": objectId})
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    c.JSON(http.StatusOK, gin.H{"message": "delete success"})
}

// GET /recipes/:id
func (handler *RecipesHandler) GetOneRecipeHandler(c *gin.Context) {
    id := c.Param("id")
    objectId, _ := primitive.ObjectIDFromHex(id)
    cur := handler.collection.FindOne(handler.ctx, bson.M{"_id": objectId})
    var recipe models.Recipe
    if err := cur.Decode(&recipe); err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
        return
    }
    c.JSON(http.StatusOK, recipe)
}

Main Application

package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"

    "github.com/gin-gonic/gin"
    "github.com/go-redis/redis"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
    "go.mongodb.org/mongo-driver/mongo/readpref"

    "buildginapp/handlers"
)

var err error
var client *mongo.Client
var recipesHandler *handlers.RecipesHandler

func init() {
    ctx := context.Background()
    url := "mongodb://root:[email protected]:27017/demo?authSource=admin&maxPoolSize=20"
    client, err = mongo.Connect(ctx, options.Client().ApplyURI(url))
    if err = client.Ping(context.TODO(), readpref.Primary()); err != nil {
        log.Fatal("connect to mongodb failed: ", err)
    }
    log.Println("Connected to MongoDB")
    collection := client.Database("demo").Collection("recipes")
    redisClient := redis.NewClient(&redis.Options{Addr: "192.168.0.20:6379", Password: "", DB: 0})
    log.Println("redis ping: ", redisClient.Ping().String())
    recipesHandler = handlers.NewRecipesHandler(ctx, collection, redisClient)
}

func main() {
    gin.SetMode(gin.ReleaseMode)
    router := gin.Default()
    router.GET("/recipes", recipesHandler.ListRecipesHandler)
    router.POST("/recipes", recipesHandler.NewRecipeHandler)
    router.PUT("/recipes/:id", recipesHandler.UpdateRecipeHandler)
    router.DELETE("/recipes/:id", recipesHandler.DeleteRecipeHandler)
    router.GET("/recipes/:id", recipesHandler.GetOneRecipeHandler)

    srv := &http.Server{Addr: "127.0.0.1:8080", Handler: router}
    go func() {
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatal("listen failed, ", err)
        }
    }()

    quit := make(chan os.Signal, 1)
    signal.Notify(quit, os.Interrupt, syscall.SIGTERM, syscall.SIGINT)
    <-quit
    ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
    defer cancel()
    if err := srv.Shutdown(ctx); err != nil {
        log.Fatalf("server shutdown failed, err: %v
", err)
    }
    log.Println("server shutdown")
}

Appendix – Data Preparation

package main

import (
    "context"
    "encoding/json"
    "fmt"
    "io/ioutil"
    "log"
    "time"

    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
    "go.mongodb.org/mongo-driver/mongo/readpref"
)

type Recipe struct {
    ID           string    `json:"id"`
    Name         string    `json:"name"`
    Tags         []string  `json:"tags"`
    Ingredients  []string  `json:"ingredients"`
    Instructions []string  `json:"instructions"`
    PublishedAt  time.Time `json:"publishedAt"`
}

var recipes []Recipe
var ctx context.Context
var err error
var client *mongo.Client

func init() {
    recipes = make([]Recipe, 0)
    file, _ := ioutil.ReadFile("recipes.json")
    json.Unmarshal([]byte(file), &recipes)
    ctx = context.Background()
    client, err = mongo.Connect(ctx, options.Client().ApplyURI("mongodb://root:[email protected]:27017/demo?authSource=admin"))
    if err = client.Ping(context.TODO(), readpref.Primary()); err != nil {
        log.Fatal(err)
    }
    collection := client.Database("demo").Collection("recipes")
    insertManyResult, err := collection.InsertMany(ctx, toInterfaceSlice(recipes))
    if err != nil {
        log.Fatal(err)
    }
    log.Println("Inserted recipes: ", len(insertManyResult.InsertedIDs))
}

func toInterfaceSlice(r []Recipe) []interface{} {
    s := make([]interface{}, len(r))
    for i, v := range r {
        s[i] = v
    }
    return s
}

func main() {
    fmt.Println("insert many data to mongodb")
}

Python Test Script

import requests
import json

def post_test(data):
    url = "http://127.0.0.1:8080/recipes"
    resp = requests.post(url, data=json.dumps(data))
    print(resp.text)

def get_test():
    url = "http://127.0.0.1:8080/recipes"
    resp = requests.get(url)
    print(resp.text)

def put_test(data, id):
    url = f"http://127.0.0.1:8080/recipes/{id}"
    resp = requests.put(url, data=json.dumps(data))
    print(json.loads(resp.text))

def delete_test(id):
    url = f"http://127.0.0.1:8080/recipes/{id}"
    resp = requests.delete(url)
    print("delete test")
    print(resp.text)

def get_test_search(id):
    url = f"http://127.0.0.1:8080/recipes/{id}"
    resp = requests.get(url)
    print("get test search")
    print(resp.text)

if __name__ == "__main__":
    data1 = {
        "name": "Homemade Pizza",
        "tags": ["italian", "pizza", "dinner"],
        "ingredients": ["1 1/2 cups warm water", "1 package active dry yeast", "3 3/4 cups bread flour", "feta cheese, firm mozzarella cheese, grated"],
        "instructions": ["Step 1.", "Step 2.", "Step 3."]
    }
    data2 = {
        "name": "西红柿炒鸡蛋",
        "tags": ["家常菜", "新手必会"],
        "ingredients": ["2个鸡蛋", "1个番茄, 切片", "葱花, 蒜瓣等"],
        "instructions": ["步骤1", "步骤2", "步骤3"]
    }
    data3 = {
        "name": "蒸蛋",
        "tags": ["家常菜", "新手必会"],
        "ingredients": ["2个鸡蛋", "葱花, 蒜瓣等"],
        "instructions": ["步骤1", "步骤2", "步骤3", "步骤4"]
    }
    # post_test(data1)
    # post_test(data2)
    # put_test(data2, id="62b7d298bb2ffa932f0d213d")
    # get_test_search(id="62b6e5746202e6a3c26b0afb")
    # delete_test(id="123456")
    get_test()
Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

RedisGoCRUDMongoDBWeb APIGin
MaGe Linux Operations
Written by

MaGe Linux Operations

Founded in 2009, MaGe Education is a top Chinese high‑end IT training brand. Its graduates earn 12K+ RMB salaries, and the school has trained tens of thousands of students. It offers high‑pay courses in Linux cloud operations, Python full‑stack, automation, data analysis, AI, and Go high‑concurrency architecture. Thanks to quality courses and a solid reputation, it has talent partnerships with numerous internet firms.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.