Comprehensive Guide to Performing CRUD Operations in Go using the Gin Framework and GORM with MongoDB

Comprehensive Guide to Performing CRUD Operations in Go using the Gin Framework and GORM with MongoDB

Introduction to CRUD Operations in Go

Go is an excellent language for building robust and scalable backend APIs. In this article, we will create a CRUD (Create, Read, Update, Delete) API in Go using the powerful Gin web framework and Gorm ORM to interface with MongoDB.

We will structure the API for maintainability and code reuse. The steps will provide a detailed walkthrough to get you up and running with a production-grade CRUD Go+Gin API talking to MongoDB.

Project Architecture

For the API server codebase, we will use the following structure:

├── cmd
│   └── api
│       └── main.go (entrypoint)
├── configs
│   └── config.go (env configs)
├── internal
│   └── apis
│       └── api.go (routes)
│   └── controllers  
│       └── controllers.go (request handling)
│   └── models
│       └── models.go (data models)
│   └── repositories
│       └── repos.go (data access)
├── pkg 
│   └── database
│       └── database.go (db initialization)
├── tests
└── go.mod (dependencies)

This layout separates concerns into discrete folders providing a maintainable structure as the app grows.

Setup and Config

Let’s begin by bootstrapping a new Go project:

mkdir gocrudapi
cd gocrudapi
go mod init github.com/testuser/gocrudapi

Under configs, let’s add config.go to manage environment variables:

package configs

import "os"

type Config struct {
  MongoDBURL string
}

func GetConfig() *Config {
  return &Config{
    MongoDBURL: os.Getenv("MONGODB_URL"),
  }
}

This will hold the MongoDB connection URL from environment variables that we’ll initialize later.

Model and Repository Layer

Under internal/models, let’s define a Post model with title and content fields:

package models

type Post struct {
  ID      string `bson:"_id" json:"id"`
  Title   string `json:"title"`
  Content string `json:"content"`
}

Next, under internal/repositories let’s add CRUD methods for posts:

package repositories

import (
  "context"

  "github.com/testuser/gocrudapi/internal/models"
  "go.mongodb.org/mongo-driver/bson"
  "go.mongodb.org/mongo-driver/bson/primitive"
  "go.mongodb.org/mongo-driver/mongo"
)

const dbName = "myapp"
const postsCollection = "posts"

var collection *mongo.Collection

type PostRepo interface {
  GetAll(ctx context.Context) ([]models.Post, error)
  GetByID(ctx context.Context, id string) (models.Post, error) 
  Create(ctx context.Context, post models.Post) error
  Update(ctx context.Context, post models.Post) error
  Delete(ctx context.Context, id string) error
}

This defines our data repository interface and methods to work with posts in MongoDB.

Database Initialization

Under pkg/database, let’s add database.go:

package database

import (
  "context"
  "log"
  "time"

  "github.com/testuser/gocrudapi/configs"
  "go.mongodb.org/mongo-driver/mongo"
  "go.mongodb.org/mongo-driver/mongo/options"
)

func Initialize() *mongo.Client {

  config := configs.GetConfig()
  
  ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
  
  client, err := mongo.Connect(ctx, options.Client().ApplyURI(config.MongoDBURL))
  defer cancel()
  
  if err != nil { 
    log.Fatal(err)
  }

  return client
}

This initializes the MongoDB client to use across the app.

API Routes and Controllers

Now let’s wire up the API endpoint routes in internal/apis/api.go:

package apis

import (
  "github.com/gin-gonic/gin"
  "github.com/testuser/gocrudapi/internal/controllers"
)

func RegisterRoutes(router *gin.Engine) {

  posts := router.Group("/posts"){
    posts.GET("/", controllers.GetAllPosts)
    posts.GET("/:id", controllers.GetPostByID) 
    posts.POST("/", controllers.CreatePost)
    posts.PUT("/:id", controllers.UpdatePost) 
    posts.DELETE("/:id", controllers.DeletePost)
  }

}

And we’ll add the route handlers in internal/controllers/controllers.go:

package controllers

import (
  "context"
  "net/http"

  "github.com/gin-gonic/gin"
  "github.com/testuser/gocrudapi/internal/models"
  "github.com/testuser/gocrudapi/internal/repositories"
  "go.mongodb.org/mongo-driver/bson/primitive"
)

var postRepo repositories.PostRepo

// GetAllPosts fetches all posts
func GetAllPosts(ctx *gin.Context) {
  
  posts, err := postRepo.GetAll(context.TODO())

  if err != nil {
    ctx.AbortWithStatus(http.StatusInternalServerError) 
    return
  }

  ctx.JSON(http.StatusOK, posts)
}

// GetPostByID fetches post by id
func GetPostByID(ctx *gin.Context) {

  id := ctx.Param("id")
  
  post, err := postRepo.GetByID(context.TODO(), id)

  if err != nil {
    ctx.AbortWithStatus(http.StatusNotFound)
    return
  }

  ctx.JSON(http.StatusOK, post)
}

// CreatePost creates a new post
func CreatePost(ctx *gin.Context) {

  var post models.Post
  if err := ctx.ShouldBindJSON(&post); err != nil {
    ctx.AbortWithStatus(http.StatusBadRequest)
    return
  }

  err := postRepo.Create(context.TODO(), post)
  if err != nil {
    ctx.AbortWithStatus(http.StatusInternalServerError)
    return
  }

  ctx.JSON(http.StatusCreated, post)
}

// UpdatePost updates an existing post
func UpdatePost(ctx *gin.Context) {

  id := ctx.Param("id")

  var post models.Post
  if err := ctx.ShouldBindJSON(&post); err != nil {
    ctx.AbortWithStatus(http.StatusBadRequest)
    return
  }

  post.ID = id

  err := postRepo.Update(context.TODO(), post)
  if err != nil {
    ctx.AbortWithStatus(http.StatusInternalServerError)
    return
  }

  ctx.JSON(http.StatusOK, post)
}

// DeletePost deletes a post
func DeletePost(ctx *gin.Context) {

  id := ctx.Param("id")

  err := postRepo.Delete(context.TODO(), id)
  if err != nil {
    ctx.AbortWithStatus(http.StatusInternalServerError)
    return
  }

  ctx.Status(http.StatusNoContent) 
}

Tying It Together

Finally, let’s complete main.go under cmd/api to run the API server:

package main

import (
  "os"

  "github.com/gin-gonic/gin"
  "github.com/joho/godotenv"
  "github.com/testuser/gocrudapi/configs"
  "github.com/testuser/gocrudapi/internal/apis"
  "github.com/testuser/gocrudapi/pkg/database"
)

func main() {

  err := godotenv.Load()
  if err != nil {
    log.Fatal("Error loading .env file")
  }

  mongoClient := database.Initialize()

  router := gin.Default()
  
  apis.RegisterRoutes(router)

  router.Run()
}

This loads the environment, initializes MongoDB, sets up routing, and runs the API server.

With this structured foundation integrating Gin, Gorm, and MongoDB – we can build a robust production-ready CRUD Operations in Go!

Running the API

To run the API:

  1. Start MongoDB
  2. Create a .env file with the MONGODB_URL connection string
  3. go run cmd/api/main.go

The API will be live on http://localhost:8080!

Conclusion

In this article, we built a cleanly architected CRUD Operations in Go leveraging some of its most useful frameworks – Gin and Gorm.

Combined with MongoDB’s flexible document model, we can quickly build powerful and scalable backends.

The code examples and structure provided here serve as a great starting point for developing real-world Go web services.

Leave a Reply

Your email address will not be published. Required fields are marked *