Skip to main content

Command Palette

Search for a command to run...

🏗 Building a CRUD API in Go Using MongoDB

Updated
18 min read
🏗 Building a CRUD API in Go Using MongoDB

In this post, we’ll build a simple blogging API using Go + MongoDB, covering full CRUD (Create, Read, Update, Delete) functionality.
By the end, you’ll understand:

  • How to connect Go with MongoDB

  • How to structure your project

  • How to make your database operations safe and efficient

What Does This Project Do?

  • RESTful API for Blog Posts:
    Provides endpoints to create, read, update, and delete blog posts.

  • Tech Stack:
    Built with Go, Gin (web framework), and MongoDB (database).

  • Features:

    • CRUD operations for blog posts

    • MongoDB integration

    • Auto-incrementing numeric IDs

    • Structured API responses

    • Clean code architecture

    • Docker support for MongoDB

We need to have a running MongoDB instance as a database, for this project we can run this as a container on same machine.

🧰 Prerequisites

Make sure you have the following installed:

  • Go 1.21+

  • Docker

  • Basic knowledge of Go syntax and REST APIs

First things first we need to set up a mongodb instance for simplicity I’m using a docker instance for setting this up

🐳 Step 1: Run MongoDB with Docker

Let’s start by spinning up a MongoDB instance locally.

docker run -d \
  --name mongodb \
  -p 27017:27017 \
  -e MONGO_INITDB_ROOT_USERNAME=admin \
  -e MONGO_INITDB_ROOT_PASSWORD=adminpass \
  mongo

✅ Verify that MongoDB is running:

docker ps

MongoDB should now be available on mongodb://localhost:27017.

To use a nosql database with golang, we can use a driver.
For mongodb we can use mongo-go-driver

⚙️ Step 2: Install MongoDB Driver for Go

Run the following command inside your Go project:

go get go.mongodb.org/mongo-driver/mongo

We’ll use Go’s official MongoDB driver to interact with our database.

Thinking out loud about using mongodb and golang, we can consider using real powers of golang via contexts, that is we can set some timeout for connections to mongodb or performing any operations under certain time. To do this we’d have to use “contexts”

Contexts & Mutex in Golang

Context

In Go, a context.Context is used to manage deadlines, cancellation signals, and request-scoped values across API boundaries. In this project, contexts are created with timeouts to ensure database operations do not hang indefinitely. This helps gracefully handle slow or unresponsive external resources.

Mutex

A sync.Mutex is a locking mechanism that ensures only one goroutine can access a critical section of code at a time. Here, a mutex is used when generating new blog post IDs to prevent race conditions if multiple requests arrive simultaneously. This guarantees thread-safe, consistent data updates in concurrent environments.

Project Structure & Flow

Main Folders

  • db
    MongoDB connection and database operations.

  • handler
    HTTP request handlers and utility functions.

  • models
    Data models and response structures.

blogging_platform/
├── db/
│   └── mongo.go       # MongoDB connection and operations
├── handler/
│   ├── handler.go     # API endpoint handlers
│   └── utils.go       # Utility functions for handlers
├── models/
│   └── blog_post.go   # Data models and response structures
├── main.go            # Application entry point
└── README.md          # Project documentation

Models folder

  • Defines the core data structures for blog posts and API responses.

  • Provides helper functions to create standardized success and error responses for the API.

models/blog_post.go

// Package models contains shared data structures
package models

import (
    "time"
)

// BlogPost represents a blog post entity
type BlogPost struct {
    ID        string    `json:"id" bson:"id_str,omitempty"`
    NumericID int       `json:"numeric_id" bson:"id"`
    Title     string    `json:"title" binding:"required" bson:"title"`
    Content   string    `json:"content" binding:"required" bson:"content"`
    Category  string    `json:"category" binding:"required" bson:"category"`
    Tags      []string  `json:"tags" binding:"required" bson:"tags"`
    CreatedAt time.Time `json:"created_at,omitempty" bson:"createdAt"`
    UpdatedAt time.Time `json:"updated_at,omitempty" bson:"updatedAt,omitempty"`
}

// Response represents a standard API response
type Response struct {
    Success bool        `json:"success"`
    Message string      `json:"message,omitempty"`
    Error   string      `json:"error,omitempty"`
    Data    interface{} `json:"data,omitempty"`
    Count   int         `json:"count,omitempty"`
}

// NewSuccessResponse creates a standardized success response
func NewSuccessResponse(message string, data interface{}) Response {
    return Response{
        Success: true,
        Message: message,
        Data:    data,
    }
}

// NewErrorResponse creates a standardized error response
func NewErrorResponse(message string) Response {
    return Response{
        Success: false,
        Error:   message,
    }
}

Package & Imports

  • Declares this file as part of the models package, used for shared data types.

  • Imports the time package for timestamp fields.

Types

1. BlogPost struct

  • Represents a blog post entity stored in MongoDB and used in API requests/responses.

  • Fields:

    • ID: String identifier (for API use).

    • NumericID: Integer identifier (for database use, auto-incremented).

    • Title: Title of the blog post (required).

    • Content: Content/body of the post (required).

    • Category: Category of the post (required).

    • Tags: List of tags (required).

    • CreatedAt: Timestamp when the post was created.

    • UpdatedAt: Timestamp when the post was last updated.

2. Response struct

  • Standardized structure for API responses.

  • Fields:

    • Success: Indicates if the request was successful.

    • Message: Optional message for success.

    • Error: Optional error message.

    • Data: Payload (can be any type).

    • Count: Optional count (e.g., number of items returned).

Functions

NewSuccessResponse(message string, data interface{}) Response

  • Creates a success response with a message and data payload.

  • Sets Success to true.

NewErrorResponse(message string) Response

  • Creates an error response with an error message.

  • Sets Success to false.

Summary

  • This file centralizes the definition of blog post data and API response formats.

  • Ensures consistent data handling and response formatting across the application.

DB Folder

Purpose

  • Manages MongoDB database connectivity and operations for the blogging platform.

  • Provides functions for initializing the database, generating unique IDs, and accessing collections

// Package db provides MongoDB database connectivity and operations
package db

import (
    "context"
    "errors"
    "log"
    "sync"
    "time"

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

// Constants for configuration
const (
    DatabaseName       = "blog_db"
    BlogPostCollection = "blog_posts"
    ConnectTimeout     = 5 * time.Second
    OperationTimeout   = 5 * time.Second
)

// Global variables
var (
    client *mongo.Client
    mutex  sync.Mutex
    once   sync.Once
)

// DbInit initializes the MongoDB connection
// This should be called once at application startup
func DbInit() error {
    var initErr error
    // Use sync.Once to ensure this only runs once
    once.Do(func() {
        initErr = initializeClient()
    })
    return initErr
}

// initializeClient creates and configures the MongoDB client
func initializeClient() error {
    uri := "mongodb://admin:adminpass@localhost:27017"

    ctx, cancel := context.WithTimeout(context.Background(), ConnectTimeout)
    defer cancel()

    var err error
    client, err = mongo.Connect(options.Client().ApplyURI(uri))
    if err != nil {
        log.Printf("Failed to connect to MongoDB: %v", err)
        return err
    }

    err = client.Ping(ctx, readpref.Primary())
    if err != nil {
        log.Printf("Failed to ping MongoDB: %v", err)
        return err
    }

    log.Printf("Successfully connected to MongoDB")
    return nil
}

// GetNextID returns a unique incremental ID by finding the max existing ID and adding 1
func GetNextID() (int, error) {
    // Use mutex to prevent race conditions if multiple requests come in simultaneously
    mutex.Lock()
    defer mutex.Unlock()
    ctx, cancel := context.WithTimeout(context.Background(), OperationTimeout)
    defer cancel()
    if client == nil {
        return 0, errors.New("database not initialized")
    }

    // Get the blog_posts collection
    collection := client.Database(DatabaseName).Collection(BlogPostCollection)

    // Find the document with the highest ID
    // Sort by ID in descending order and get the first document
    opts := options.FindOne().SetSort(bson.M{"id": -1})
    var result bson.M

    err := collection.FindOne(ctx, bson.M{}, opts).Decode(&result)
    if err != nil {
        // If no documents exist yet, start with ID 1
        return 1, nil
    }

    // Get the highest ID
    highestID := 0
    if idVal, exists := result["id"]; exists {
        // Convert to int if possible
        if idInt, ok := idVal.(int32); ok {
            highestID = int(idInt)
        } else if idInt, ok := idVal.(int64); ok {
            highestID = int(idInt)
        } else if idInt, ok := idVal.(int); ok {
            highestID = idInt
        }
    }
    // Return highest ID + 1
    return highestID + 1, nil
}

// GetDB returns the database instance
func GetDB() *mongo.Database {
    if client == nil {
        log.Printf("Warning: Database client is nil. Make sure DbInit() was called.")
        return nil
    }
    return client.Database(DatabaseName)
}

// Close gracefully disconnects from MongoDB
func Close() error {
    if client == nil {
        return nil
    }
    ctx, cancel := context.WithTimeout(context.Background(), ConnectTimeout)
    defer cancel()
    if err := client.Disconnect(ctx); err != nil {
        log.Printf("Failed to disconnect from MongoDB: %v", err)
        return err
    }
    log.Printf("Successfully disconnected from MongoDB")
    return nil
}

Package & Imports

  • Declares the package for database-related code.

  • Imports:

    • context, errors, log, sync, time: Standard Go packages for concurrency, error handling, logging, and timeouts.

    • MongoDB driver packages: For connecting and interacting with MongoDB.

Constants & Global Variables

  • DatabaseName: Name of the MongoDB database (blog_db).

  • BlogPostCollection: Name of the collection for blog posts (blog_posts).

  • ConnectTimeout, OperationTimeout: Timeouts for connecting and performing DB operations.

  • client: MongoDB client instance (used for all DB operations).

  • mutex, once: Synchronization primitives to ensure thread-safe initialization.

Functions in mongo.go

DbInit() error

  • Initializes the MongoDB client for the application.

  • Uses sync.Once to ensure initialization happens only once (thread safety).

  • Calls initializeClient() to set up the connection.

  • Returns any error encountered during initialization.

initializeClient() error

  • Sets up the MongoDB connection URI:
    Uses credentials (admin:adminpass) and host (localhost:27017) to form the connection string.

  • Creates a timeout context:
    Ensures the connection attempt does not hang indefinitely by using ConnectTimeout.

  • Attempts to connect to MongoDB:
    Calls mongo.Connect() with the URI and context.
    If connection fails, logs the error and returns it.

  • Pings the MongoDB server:
    Verifies the connection is alive and the server is reachable.
    If ping fails, logs the error and returns it.

  • Logs success:
    If both connection and ping succeed, logs a success message.

  • Returns error status:

    If any step fails, the function returns the error, allowing the caller to handle it (e.g., abort startup or retry).
    If all steps succeed, returns nil (no error).

GetNextID() (int, error)

  • Locks a mutex to prevent concurrent access (thread safety).

  • Creates a timeout context for the database operation.

  • Checks if the MongoDB client is initialized; returns error if not.

  • Gets the blog post collection from the database.

  • Finds the document with the highest id by sorting in descending order.

  • If no documents exist, returns 1 as the starting ID.

  • If a document exists, extracts the highest id (handles multiple integer types).

  • Returns the next available ID (highestID + 1).

GetDB() *mongo.Database

  • Checks if the MongoDB client is initialized.

  • Logs a warning if the client is nil (not initialized).

Returns the database instance for use in other parts of the application.

Close() error

  • Checks if the MongoDB client is initialized; does nothing if nil.

  • Creates a timeout context for the disconnect operation.

  • Calls client.Disconnect() to gracefully close the connection.

  • Logs any error encountered during disconnect.

  • Returns the error status (or nil if successful).

Entry Point : main.go

  • Initializes MongoDB connection

  • Sets up Gin router and API endpoints:

    • /ping (health check)

    • /posts (POST: create, GET: list)

    • /posts/:id (PUT: update, DELETE: delete)

  • Starts the web server

Reading main.go

// Main.go file which initializes mongodb and API
package main

import (
    "blogging_platform/db"
    "blogging_platform/handler"
    "github.com/gin-gonic/gin"
)

func main() {
    // Initialize mongodb connection
    db.DbInit()
    router := gin.Default()
    router.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })

    router.POST("/posts", handler.CreateBlogPost)
    // curl -X POST http://localhost:8080/posts   -H "Content-Type: application/json"   -d '{"title":"My First blog Post","content":"This is the content of my first blog post.","category":"Technology","tags":["Tech","Programming"]}'
    router.GET("/posts", handler.GetBlogPost)
    // curl -X GET http://localhost:8080/posts
    router.DELETE("/posts/:id", handler.DeleteBlogPost)
    //curl -X DELETE http://localhost:8080/posts/2
    router.PUT("/posts/:id", handler.UpdateBlogPost)
    //# Update a blog post with ID 1
    //curl -X PUT http://localhost:8080/posts/1 \
    //  -H "Content-Type: application/json" \
    //  -d '{"title":"Updated Title","content":"Updated content","category":"Updated Category","tags":["updated","new"]}'

    router.Run() // listen and serve on 0.0.0.0:8080
}

Package & Imports

  • package main

    • Declares this file as the main package (entry point for Go programs).
  • import ( ... )

    • "blogging_platform/db": Imports database connection and operations.

    • "blogging_platform/handler": Imports HTTP handler functions for API endpoints.

    • "github.com/gin-gonic/gin": Imports Gin web framework for routing and HTTP server.

Main function

  • Initializes MongoDB connection (sets up client, connects to DB).

  • Creates a new Gin router with default middleware (logger, recovery).

  • Defines a health check endpoint /ping that returns JSON { "message": "pong" }.

  • Registers POST /posts endpoint to create a new blog post (handled by CreateBlogPost).

  • Registers GET /posts endpoint to retrieve all blog posts (handled by GetBlogPost).

  • Registers DELETE /posts/:id endpoint to delete a blog post by ID (handled by DeleteBlogPost).

    • The /:id in route paths (e.g., /posts/:id) is a route parameter in Gin (and most web frameworks).

    • It acts as a placeholder for a specific value, allowing the API to handle requests for different resources dynamically.

    • For example, a request to /posts/5 will match /posts/:id and set id to 5.

    • In handler functions, you can access this value using c.Param("id") to identify which blog post to update or delete.

  • Registers PUT /posts/:id endpoint to update a blog post by ID (handled by UpdateBlogPost).

  • Starts the HTTP server on 0.0.0.0:8080 (default Gin port).

Handler folder

  • Contains all HTTP request handler functions for the API endpoints.

  • Provides utility functions for request validation, context management, and standardized responses.

handler/handler.go

  • Implements the main logic for each API endpoint (CRUD operations for blog posts).
package handler

import (
    "blogging_platform/db"
    "blogging_platform/models"
    "fmt"
    "net/http"
    "strconv"
    "time"

    "github.com/gin-gonic/gin"
    "go.mongodb.org/mongo-driver/v2/bson"
    "go.mongodb.org/mongo-driver/v2/mongo/options"
)

// CreateBlogPost handles the creation of a new blog post
func CreateBlogPost(c *gin.Context) {
    var newPost models.BlogPost
    // Parse request body
    if err := c.ShouldBindJSON(&newPost); err != nil {
        sendError(c, http.StatusBadRequest, "Invalid input: "+err.Error())
        return
    }
    // Validate required fields
    if !validateBlogPost(&newPost) {
        sendError(c, http.StatusBadRequest, "All fields are required")
        return
    }
    // Create context with timeout
    ctx, cancel := createContext()
    defer cancel()
    // Get database connection
    database := db.GetDB()
    if database == nil {
        sendError(c, http.StatusInternalServerError, "Database connection error")
        return
    }

    collection := database.Collection(db.BlogPostCollection)

    // Get next ID
    postID, err := db.GetNextID()
    if err != nil {
        sendError(c, http.StatusInternalServerError, "Error generating ID: "+err.Error())
        return
    }

    // Set post metadata
    newPost.NumericID = postID
    newPost.ID = fmt.Sprintf("%d", postID)
    newPost.CreatedAt = time.Now()

    // Insert the document
    result, err := collection.InsertOne(ctx, newPost)
    if err != nil {
        sendError(c, http.StatusInternalServerError, "Failed to create blog post: "+err.Error())
        return
    }

    // Return success response
    data := map[string]interface{}{
        "id":       postID,
        "mongo_id": result.InsertedID,
        "post":     newPost,
    }
    sendSuccess(c, http.StatusCreated, "Blog post created successfully", data)
}

// GetBlogPost retrieves all blog posts
func GetBlogPost(c *gin.Context) {
    // Create context with timeout
    ctx, cancel := createContext()
    defer cancel()

    database := db.GetDB()
    if database == nil {
        sendError(c, http.StatusInternalServerError, "Database connection error")
        return
    }

    collection := database.Collection(db.BlogPostCollection)

    // Options to sort by ID in ascending order
    opts := options.Find().SetSort(bson.M{"id": 1})

    // Execute the query
    cursor, err := collection.Find(ctx, bson.M{}, opts)
    if err != nil {
        sendError(c, http.StatusInternalServerError, "Failed to retrieve blog posts: "+err.Error())
        return
    }
    defer cursor.Close(ctx)

    // Decode all documents into a slice
    var posts []models.BlogPost
    if err := cursor.All(ctx, &posts); err != nil {
        sendError(c, http.StatusInternalServerError, "Failed to decode blog posts: "+err.Error())
        return
    }

    // Return the posts as JSON
    data := map[string]interface{}{
        "count": len(posts),
        "posts": posts,
    }
    sendSuccess(c, http.StatusOK, "Blog posts retrieved successfully", data)
}

// DeleteBlogPost deletes a blog post by ID
func DeleteBlogPost(c *gin.Context) {
    // Get post ID from URL parameter
    postID := c.Param("id")
    if postID == "" {
        sendError(c, http.StatusBadRequest, "Post ID is required")
        return
    }

    // Convert ID string to int for query
    id, err := strconv.Atoi(postID)
    if err != nil {
        sendError(c, http.StatusBadRequest, "Invalid post ID format")
        return
    }

    // Create context with timeout
    ctx, cancel := createContext()
    defer cancel()

    // Get database connection
    database := db.GetDB()
    if database == nil {
        sendError(c, http.StatusInternalServerError, "Database connection error")
        return
    }

    collection := database.Collection(db.BlogPostCollection)

    // Delete the post with matching ID
    result, err := collection.DeleteOne(ctx, bson.M{"id": id})
    if err != nil {
        sendError(c, http.StatusInternalServerError, "Failed to delete post: "+err.Error())
        return
    }

    // Check if any document was deleted
    if result.DeletedCount == 0 {
        sendError(c, http.StatusNotFound, "No post found with that ID")
        return
    }

    // Return success response
    data := map[string]interface{}{
        "id": postID,
    }
    sendSuccess(c, http.StatusOK, "Blog post deleted successfully", data)
}

// UpdateBlogPost updates an existing blog post
func UpdateBlogPost(c *gin.Context) {
    // Get post ID from URL parameter
    postID := c.Param("id")
    if postID == "" {
        sendError(c, http.StatusBadRequest, "Post ID is required")
        return
    }

    // Convert ID string to int for query
    id, err := strconv.Atoi(postID)
    if err != nil {
        sendError(c, http.StatusBadRequest, "Invalid post ID format")
        return
    }

    // Parse the updated post data
    var updatedPost models.BlogPost
    if err := c.ShouldBindJSON(&updatedPost); err != nil {
        sendError(c, http.StatusBadRequest, "Invalid input: "+err.Error())
        return
    }

    // Validate required fields
    if !validateBlogPost(&updatedPost) {
        sendError(c, http.StatusBadRequest, "All fields are required")
        return
    }

    // Create context with timeout
    ctx, cancel := createContext()
    defer cancel()

    // Get database connection
    database := db.GetDB()
    if database == nil {
        sendError(c, http.StatusInternalServerError, "Database connection error")
        return
    }

    collection := database.Collection(db.BlogPostCollection)

    // First check if the post exists
    var existingPost models.BlogPost
    err = collection.FindOne(ctx, bson.M{"id": id}).Decode(&existingPost)
    if err != nil {
        sendError(c, http.StatusNotFound, "Post not found")
        return
    }

    // Create update document
    update := bson.M{
        "$set": bson.M{
            "title":     updatedPost.Title,
            "content":   updatedPost.Content,
            "category":  updatedPost.Category,
            "tags":      updatedPost.Tags,
            "updatedAt": time.Now(),
        },
    }

    // Update the document
    result, err := collection.UpdateOne(ctx, bson.M{"id": id}, update)
    if err != nil {
        sendError(c, http.StatusInternalServerError, "Failed to update post: "+err.Error())
        return
    }

    // Check if any document was updated
    if result.ModifiedCount == 0 {
        sendError(c, http.StatusNotFound, "No post was updated")
        return
    }

    // Set the ID in the response
    updatedPost.ID = postID
    updatedPost.NumericID = id

    // Return success response
    data := map[string]interface{}{
        "id":   id,
        "post": updatedPost,
    }
    sendSuccess(c, http.StatusOK, "Blog post updated successfully", data)
}

Package & Imports

  • Declares the handler package for API logic.

  • Imports:

    • Project packages: db, models

    • Gin web framework

    • MongoDB driver

    • Standard Go packages: fmt, net/http, strconv, time

Main Handler Functions

CreateBlogPost(c *gin.Context)

  • Declares a new variable newPost of type BlogPost struct.

  • Uses ShouldBindJSON to parse and bind incoming JSON to newPost.

  • Validates required fields using validateBlogPost.

  • Creates a context with timeout for DB operations.

  • Gets the database connection and blog post collection.

  • Calls GetNextID to generate a new unique ID.

  • Sets metadata: numeric ID, string ID, creation timestamp.

  • Inserts the new post into MongoDB using InsertOne operation.

  • Returns a standardized success response with post details, or error if any step fails.

GetBlogPost(c *gin.Context)

  • Creates a context with timeout for DB operations.

  • Gets the database connection and blog post collection.

  • Sets query options to sort posts by ID in ascending order.

  • Executes a query to find all blog posts.

  • Decodes all documents into a slice of BlogPost.

  • Returns a standardized success response with the list and count of posts, or error if any step fails.

DeleteBlogPost(c *gin.Context)

  • Retrieves the post ID from the URL parameter using c.Param("id").

  • Converts the ID string to an integer for querying.

  • Creates a context with timeout for DB operations.

  • Gets the database connection and blog post collection.

  • Executes a delete operation for the post with the matching ID.

  • Checks if any document was deleted (DeletedCount).

  • Returns a success response if deleted, or error if not found or if any step fails.

UpdateBlogPost(c *gin.Context)

  • Retrieves the post ID from the URL parameter.

  • Converts the ID string to an integer for querying.

  • Declares a new variable updatedPost of type BlogPost.

  • Uses ShouldBindJSON to parse and bind incoming JSON to updatedPost.

  • Validates required fields using validateBlogPost.

  • Creates a context with timeout for DB operations.

  • Gets the database connection and blog post collection.

  • Checks if the post exists by querying with the ID.

  • Creates an update document with new values and current timestamp.

  • Executes the update operation in MongoDB.

  • Returns a success response if updated, or error if not found or if any step fails.

Note on JSON Binding in Gin

  • Do not use BindJSON or ShouldBindJSON if you need to bind the request body multiple times.

  • These methods consume the request body stream; once read, it cannot be read again.

  • If you need to bind the body more than once, use ShouldBindBodyWith or ShouldBindBody instead.

  • See: Gin Issue #439 for details.

  • Using BindJSON/ShouldBindJSON twice in the same handler will cause errors because the body is already consumed.

handler/utils.go

  • Provides utility functions to support the main handler logic.

  • Centralizes common tasks like context creation, response formatting, and input validation.

// Package handler contains HTTP request handlers
package handler

import (
    "blogging_platform/models"
    "context"
    "time"

    "github.com/gin-gonic/gin"
)

const (
    // DefaultTimeout for context operations
    DefaultTimeout = 5 * time.Second
)

// createContext creates a timeout context for database operations
func createContext() (context.Context, context.CancelFunc) {
    return context.WithTimeout(context.Background(), DefaultTimeout)
}

// sendError sends a standardized error response
func sendError(c *gin.Context, statusCode int, message string) {
    c.JSON(statusCode, models.NewErrorResponse(message))
}

// sendSuccess sends a standardized success response
func sendSuccess(c *gin.Context, statusCode int, message string, data interface{}) {
    c.JSON(statusCode, models.NewSuccessResponse(message, data))
}

// validateBlogPost validates that a blog post has all required fields
func validateBlogPost(post *models.BlogPost) bool {
    return post.Title != "" && post.Content != "" &&
        post.Category != "" && len(post.Tags) > 0
}

Package & Imports

  • Declares the file as part of the handler package.

  • Imports:

    • models: For response formatting and blog post struct.

    • context, time: For context management and timeouts.

    • github.com/gin-gonic/gin: For Gin context and response handling.

Constants

  • DefaultTimeout :Sets a default timeout (5 seconds) for database operations to prevent hanging requests.

Functions

createContext() (context.Context, context.CancelFunc)

  • Returns a new context with a timeout (DefaultTimeout).

  • Used to ensure DB operations do not run indefinitely.

sendError(c *gin.Context, statusCode int, message string)

  • Sends a standardized error response using the models.NewErrorResponse function.

  • Sets the HTTP status code and error message.

sendSuccess(c *gin.Context, statusCode int, message string, data interface{})

  • Sends a standardized success response using the models.NewSuccessResponse function.

  • Sets the HTTP status code, message, and any data payload.

validateBlogPost(post *models.BlogPost) bool

  • Checks that all required fields in a blog post are present and valid.

  • Returns true if valid, false otherwise.

Putting It All Together : Run & Test

Remember to test this out we need mongodb container running,

docker run -d \
  --name mongodb \
  -p 27017:27017 \
  -e MONGO_INITDB_ROOT_USERNAME=admin \
  -e MONGO_INITDB_ROOT_PASSWORD=adminpass \
  mongo:latest

then run

jinx@MSI:~/go/GO_PROJECTS/blogging_platform$ go run main.go
2025/08/31 15:30:37 Successfully connected to MongoDB
[GIN-debug] [WARNING] Creating an Engine instance with the Logger and Recovery middleware already attached.

[GIN-debug] [WARNING] Running in "debug" mode. Switch to "release" mode in production.
 - using env:   export GIN_MODE=release
 - using code:  gin.SetMode(gin.ReleaseMode)

[GIN-debug] GET    /ping                     --> main.main.func1 (3 handlers)
[GIN-debug] POST   /posts                    --> blogging_platform/handler.CreateBlogPost (3 handlers)
[GIN-debug] GET    /posts                    --> blogging_platform/handler.GetBlogPost (3 handlers)
[GIN-debug] DELETE /posts/:id                --> blogging_platform/handler.DeleteBlogPost (3 handlers)
[GIN-debug] PUT    /posts/:id                --> blogging_platform/handler.UpdateBlogPost (3 handlers)
[GIN-debug] [WARNING] You trusted all proxies, this is NOT safe. We recommend you to set a value.
Please check https://pkg.go.dev/github.com/gin-gonic/gin#readme-don-t-trust-all-proxies for details.
[GIN-debug] Environment variable PORT is undefined. Using port :8080 by default
[GIN-debug] Listening and serving HTTP on :8080

🧪Testing out the web server

jinx@MSI:~$ curl http://localhost:8080/ping
{"message":"pong"}

Creating first Blog Post

curl -X POST http://localhost:8080/posts \
  -H "Content-Type: application/json" \
  -d '{
    "title": "My First Blog Post",
    "content": "This is the content of my first blog post.",
    "category": "Technology",
    "tags": ["Tech", "Programming"]
  }'

We got a success response, let’s try to get back all blog post list using:

curl http://localhost:8080/posts

Updating a blog post:

curl -X PUT http://localhost:8080/posts/1 \
  -H "Content-Type: application/json" \
  -d '{
    "title": "Updated Title",
    "content": "Updated content",
    "category": "Updated Category",
    "tags": ["updated", "new"]
  }'

Deleting a Blog Post:

curl -X DELETE http://localhost:8080/posts/1

All CRUD operations for Blog Post work as expected, this is it for this Blog see you in next one.

Thank you.

More from this blog

Pranav's Blog

27 posts