🏗 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.Onceto 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:
Callsmongo.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, returnsnil(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
1as 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
/pingthat returns JSON{ "message": "pong" }.Registers POST
/postsendpoint to create a new blog post (handled byCreateBlogPost).Registers GET
/postsendpoint to retrieve all blog posts (handled byGetBlogPost).Registers DELETE
/posts/:idendpoint to delete a blog post by ID (handled byDeleteBlogPost).The
/:idin 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/5will match/posts/:idand setidto5.In handler functions, you can access this value using
c.Param("id")to identify which blog post to update or delete.
Registers PUT
/posts/:idendpoint to update a blog post by ID (handled byUpdateBlogPost).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, modelsGin 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
BlogPoststruct.Uses
ShouldBindJSONto 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
InsertOneoperation.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
ShouldBindJSONto 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
BindJSONorShouldBindJSONif 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
ShouldBindBodyWithorShouldBindBodyinstead.See: Gin Issue #439 for details.
Using
BindJSON/ShouldBindJSONtwice 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
handlerpackage.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
trueif valid,falseotherwise.
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.



