Golang CRUD REST API with MongoDB

In this tutorial, you’ll learn how to build a CRUD (Create, Read, Update, Delete) REST API using Golang and MongoDB. We’ll walk you through each step of setting up a Go project, interacting with a MongoDB database, and testing your API with Postman. This tutorial is ideal for both beginners and experienced developers looking to expand their knowledge of Golang and MongoDB integration.

Prerequisites

Before we start, ensure you have the following tools and software installed:

  1. Go (Golang) – Ensure that the latest version of Go is installed.
  2. MongoDB – MongoDB should be installed and running.
  3. Postman – A tool for testing APIs.

Step 1: Set Up Your Go Project

Start by creating a directory for your project and initializing the Go module:

mkdir go-crud-api-mongodb
cd go-crud-api-mongodb
go mod init go-crud-api-mongodb

This sets up a new Go module, which allows you to manage your project dependencies easily.

Step 2: Install Required Packages

Install the required Go packages for MongoDB and HTTP request handling:

go get go.mongodb.org/mongo-driver/mongo
go get go.mongodb.org/mongo-driver/mongo/options
go get github.com/gorilla/mux
  • go.mongodb.org/mongo-driver/mongo: Official MongoDB driver for Go.
  • github.com/gorilla/mux: A powerful routing library for creating APIs in Go.

Step 3: Set Up the MongoDB Database

3.1 Create a Database and Collection

Start MongoDB and create a new database called go_crud_api and a collection called users:

use go_crud_api;

db.createCollection("users");

This will serve as the collection where we’ll store user data.

Step 4: Create the Main Application File

Create a file called main.go to define the structure of the Go web application:

package main

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

    "github.com/gorilla/mux"
    "go.mongodb.org/mongo-driver/bson"
    "go.mongodb.org/mongo-driver/bson/primitive"
    "go.mongodb.org/mongo-driver/mongo"
    "go.mongodb.org/mongo-driver/mongo/options"
)

// User represents the model for our resource
type User struct {
    ID        primitive.ObjectID `json:"id,omitempty" bson:"_id,omitempty"`
    Name      string             `json:"name,omitempty" bson:"name,omitempty"`
    Email     string             `json:"email,omitempty" bson:"email,omitempty"`
    CreatedAt time.Time          `json:"created_at,omitempty" bson:"created_at,omitempty"`
}

var client *mongo.Client

func main() {
    // Initialize MongoDB client
    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
    defer cancel()
    clientOptions := options.Client().ApplyURI("mongodb://localhost:27017")
    client, _ = mongo.Connect(ctx, clientOptions)

    router := mux.NewRouter()
    router.HandleFunc("/users", getUsers).Methods("GET")
    router.HandleFunc("/user/{id}", getUser).Methods("GET")
    router.HandleFunc("/user", createUser).Methods("POST")
    router.HandleFunc("/user/{id}", updateUser).Methods("PUT")
    router.HandleFunc("/user/{id}", deleteUser).Methods("DELETE")

    log.Fatal(http.ListenAndServe(":8000", router))
}

Explanation of the Code

  • MongoDB Connection: We connect to MongoDB using mongo.Connect() and specify a 10-second timeout.
  • Router Setup: We use Gorilla Mux to define the routes for each API endpoint.
  • CRUD Endpoints:
    • /users: Fetch all users.
    • /user/{id}: Fetch a user by ID.
    • /user: Create a new user.
    • /user/{id}: Update a user by ID.
    • /user/{id}: Delete a user by ID.

Step 5: Implement CRUD Operations

5.1 Fetch All Users

Let's create a function that retrieves all users from the users collection and encodes them as JSON.
func getUsers(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    var users []User
    collection := client.Database("go_crud_api").Collection("users")
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    cursor, err := collection.Find(ctx, bson.M{})
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    defer cursor.Close(ctx)
    for cursor.Next(ctx) {
        var user User
        cursor.Decode(&user)
        users = append(users, user)
    }
    json.NewEncoder(w).Encode(users)
}

5.2 Fetch a Single User by ID

Let's create a function that retrieves a user by their ID from the users collection and returns it as JSON. If no user is found, it returns a 404 error.
func getUser(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    params := mux.Vars(r)
    id, _ := primitive.ObjectIDFromHex(params["id"])
    var user User
    collection := client.Database("go_crud_api").Collection("users")
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    err := collection.FindOne(ctx, bson.M{"_id": id}).Decode(&user)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    json.NewEncoder(w).Encode(user)
}

5.3 Create a New User

Let's create a function that decodes the JSON request body, creates a new user in the users collection, and returns the newly created user as JSON.
func createUser(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    var user User
    _ = json.NewDecoder(r.Body).Decode(&user)
    user.ID = primitive.NewObjectID()
    user.CreatedAt = time.Now()
    collection := client.Database("go_crud_api").Collection("users")
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    _, err := collection.InsertOne(ctx, user)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    json.NewEncoder(w).Encode(user)
}

5.4 Update an Existing User

Let's create a function that updates an existing user in the users collection by their ID and returns the updated user as JSON.
func updateUser(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    params := mux.Vars(r)
    id, _ := primitive.ObjectIDFromHex(params["id"])
    var user User
    _ = json.NewDecoder(r.Body).Decode(&user)
    collection := client.Database("go_crud_api").Collection("users")
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    update := bson.M{
        "$set": user,
    }
    _, err := collection.UpdateOne(ctx, bson.M{"_id": id}, update)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    user.ID = id
    json.NewEncoder(w).Encode(user)
}

5.5 Delete a User

Let's create a function that deletes a user from the users collection by their ID and returns a 204 No Content status on success.
func deleteUser(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    params := mux.Vars(r)
    id, _ := primitive.ObjectIDFromHex(params["id"])
    collection := client.Database("go_crud_api").Collection("users")
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    _, err := collection.DeleteOne(ctx, bson.M{"_id": id})
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    w.WriteHeader(http.StatusNoContent)
}

Testing the CRUD API with Postman

You can test the API endpoints using Postman with the following requests:

  1. Get All Users

    • Method: GET
    • URL: http://localhost:8000/users
    • Response: JSON array of users.
  2. Get a Single User

    • Method: GET
    • URL: http://localhost:8000/user/{id}
    • Response: JSON object of the user.
  3. Create a New User

    • Method: POST
    • URL: http://localhost:8000/user
    • Body (JSON):
    {
      "name": "John Doe",
      "email": "john@example.com"
    }
    
  4. Update an Existing User

    • Method: PUT
    • URL: http://localhost:8000/user/{id}
    • Body (JSON):
    • {
        "name": "John Doe",
        "email": "john@example.com"
      }
  1. Delete a User
    • Method: DELETE
    • URL: http://localhost:8000/user/{id}

Best Practices

  1. Error Handling: Implement comprehensive error handling and return appropriate HTTP status codes.
  2. Input Validation: Validate user input before interacting with MongoDB to maintain data integrity.
  3. Logging: Integrate logging to capture useful information for debugging and monitoring.
  4. Environment Variables: Use environment variables to store sensitive information like MongoDB URIs.

Conclusion

In this tutorial, you learned how to create a Golang CRUD REST API that interacts with MongoDB. We covered the project setup, API creation, and tested the APIs using Postman. You can extend this example by adding features like authentication, more complex queries, and additional validations.


Comments