DEV Community

Cover image for REST API using GO(gin) & Mongo
Deeshath
Deeshath

Posted on

REST API using GO(gin) & Mongo

In this blog, we will be building a RESTful API server with two endpoints. The example project will be a repository of data about books in a library.

This includes the following sections:

  1. Design API endpoints.
  2. Create a folder for code.
  3. Connect to MongoDB using qmgo
  4. Write handlers to
    • add a new book
    • get all books
    • get a specific book
    • delete a specific book

Prerequisites:

  • An installation of Go 1.20 or later. For installation instructions, visit.
  • An installation of MongoDB 6.0 or later. For installation instructions, visit
  • Any text editor or IDE we have will work fine.

Design API endpoints:

We’ll build an API that provides access to the books in a library. So we’ll need to provide endpoints through which a client can get, add or delete books in a library.

Here are the endpoints we’ll create in this tutorial.

/books

GET – Get a list of all books, returned as JSON.
POST – Add a new book from the request data sent as JSON.

/books/:id

GET – Get a book by its ID, returning the book data as JSON.
PATCH – Update a book by its ID, returning the book data as JSON.
DELETE – Delete a book by its ID, returning the deletion status as JSON.

Create a folder for our code:

To begin, we will create a project for the code we are about to write.

Using the command prompt, create a folder for our code called books.

$ mkdir books
$ cd books
Enter fullscreen mode Exit fullscreen mode

Create a module in which we can manage dependencies.

Run the go mod init command, giving it the path of the module our code will be in.

$ go mod init books
go: creating new go.mod: module books
Enter fullscreen mode Exit fullscreen mode

This command creates a go.mod file in which dependencies we add will be listed for tracking.

Code

Using IDE/text editor, create a file called main.go in the books directory. We’ll write our Go code in this file.

In main.go, at the top of the file, add the following package declaration and main function that will be called when we run the code since a standalone program (as opposed to a library) is always in package main.

package main

func main() {

}
Enter fullscreen mode Exit fullscreen mode

To run the program use go run .

Beneath the package declaration, let's start writing the code for Connecting to MongoDB.

Install the qmgo using the below cmd

go get github.com/gin-gonic/gin

import the "github.com/qiniu/qmgo" package and declare the variable database and collection and initialize them in the main function so that it could be used later on to perform CRUD operations on Data in the Database.

var database *qmgo.Database
var collection *qmgo.Collection

func main() {

    // create new Client
    const databaseURI = "mongodb://localhost:27017"
    fmt.Println("Connecting to database", databaseURI)
    ctx := context.Background()
    connection, err := qmgo.NewClient(ctx, &qmgo.Config{Uri: databaseURI})

    database = connection.Database("test")    // creating Database connection
    collection = database.Collection("books") // get the collection
    defer func() {
        if err = connection.Close(ctx); err != nil {
            fmt.Println("Closing Connection to database", databaseURI)
            panic(err)
        }
    }()

}
Enter fullscreen mode Exit fullscreen mode

Now we proceed on to write the handlers and configuring the app to listen to a http port (in our case 8000) in the main function.

install the gin using the below cmd

go get github.com/gin-gonic/gin
and import it in the main file

    router := gin.Default() // create router using gin

    // register routes
    router.POST("/books", CreateBook)

    fmt.Println("Service is up & running at localhost:8000")
    router.Run(":8000") // register router to port 8000
Enter fullscreen mode Exit fullscreen mode

Here we have registered a POST router let's go ahead and create the CreateBook function to handle the create request

Create a new file books.go in which we will be writing the code for handling the requests

Create the request and response structure for the books:

// form:"title" to map the JSON field name to the struct
// binding:"required" to enforce the value is required
type BookCreateUpdateRequest struct {
    Title  string `form:"title" binding:"required"`
    Author string `form:"author"`
}

// json:"id" to map the struct Name to its Json field name
type BookResponse struct {
    Id        primitive.ObjectID `json:"id"`
    Title     string             `json:"title"`
    Author    string             `json:"author"`
    CreatedAt time.Time          `json:"createdAt"`
    UpdatedAt time.Time          `json:"updatedAt"`
}
Enter fullscreen mode Exit fullscreen mode

Create the Database model structure:

type Book struct {
    field.DefaultField `bson:"inline"`
    Title              string `bson:"title" validate:"required"`
    Author             string `bson:"author"`
}
Enter fullscreen mode Exit fullscreen mode

Let's create the CreateBookmethod to handle book creation requests.

func CreateBook(ctx *gin.Context) {
    var newBook BookCreateUpdateRequest

    // to bind the received JSON to BookRequest to strip the unnecessary fields.
    if err := ctx.ShouldBind(&newBook); err != nil {
        ctx.JSON(http.StatusBadRequest, "Invalid Request")
        return
    }

    // setting data to book model struct
    book := Book{
        Title:  newBook.Title,
        Author: newBook.Author,
    }
    _, err := collection.InsertOne(ctx, &book) //Inserting the Book Data to database

    // to send error response if any error occurs
    if err != nil {
        ctx.JSON(http.StatusInternalServerError, "Something went wrong, Try again after sometime")
        return
    }

    // to send success response on completion
    ctx.JSON(http.StatusCreated, GetBooksResponse(book))
}

func GetBooksResponse(book Book) (bookResponse BookResponse) {
    // setting response for book
    bookResponse = BookResponse{
        Id:        book.DefaultField.Id,
        Title:     book.Title,
        Author:    book.Author,
        CreatedAt: book.CreateAt,
        UpdatedAt: book.UpdateAt,
    }
    return
}
Enter fullscreen mode Exit fullscreen mode

In the above code, we use GetBooksResponse method to set the response that we are to send (we will be using this for other API responses too).

Below are the Handlers for Get, List, Update and Delete.

Get a book detail API:

func GetBook(ctx *gin.Context) {

    // to get and convert the received path variable to  desired type
    bookId, err := primitive.ObjectIDFromHex(ctx.Param("bookId"))
    if err != nil {
        ctx.JSON(http.StatusBadRequest, "Invalid Request")
        return
    }

    //Getting the Book Data from database
    var book Book
    err = collection.Find(ctx, bson.M{"_id": bookId}).One(&book)

    // to send error response if any error occurs
    if err != nil {
        ctx.JSON(http.StatusNotFound, "Book Not Found")
        return
    }

    // to send success response on completion
    ctx.JSON(http.StatusOK, GetBooksResponse(book))
}
Enter fullscreen mode Exit fullscreen mode

In the above code the bson.M{"_id": bookId} is used to find the book by Id and One is used to bind the mongo Data to the book variable

Update a book API:

func UpdateBook(ctx *gin.Context) {

    // to get and convert the received path variable to  desired type
    bookId, err := primitive.ObjectIDFromHex(ctx.Param("bookId"))
    if err != nil {
        ctx.JSON(http.StatusBadRequest, "Invalid Book ID")
        return
    }

    var newBook BookCreateUpdateRequest

    // to bind the received JSON to BookRequest to strip the unnecessary fields.
    if err := ctx.ShouldBind(&newBook); err != nil {
        ctx.JSON(http.StatusBadRequest, "Invalid Request")
        return
    }

    //Getting the Book Data from database
    var book Book
    err = collection.Find(ctx, bson.M{"_id": bookId}).One(&book)

    // to send error response if any error occurs
    if err != nil {
        ctx.JSON(http.StatusNotFound, "Book Not Found")
        return
    }

    // set the updated value in the book
    book.Author = newBook.Author
    book.Title = newBook.Title

    // update in database
    err = collection.ReplaceOne(ctx, bson.M{"_id": bookId}, &book)
    if err != nil {
        ctx.JSON(http.StatusInternalServerError, "Something went wrong, Try again after sometime")
        return
    }

    // to send success response on completion
    ctx.JSON(http.StatusOK, GetBooksResponse(book))
}
Enter fullscreen mode Exit fullscreen mode

In the above code block, collection.ReplaceOne is used to replace the existing document based on the condition the ReplaceOne method also updates the default field UpdateAT in the database.

Delete Book Handler:

func DeleteBook(ctx *gin.Context) {

    // to get and convert the received path variable to  desired type
    bookId, err := primitive.ObjectIDFromHex(ctx.Param("bookId"))
    if err != nil {
        ctx.JSON(http.StatusBadRequest, "Invalid Request")
        return
    }

    //Getting the Book Data from database
    var book Book
    err = collection.Find(ctx, bson.M{"_id": bookId}).One(&book)

    // to send error response if any error occurs
    if err != nil {
        ctx.JSON(http.StatusNotFound, "Book Not Found")
        return
    }

    // Deleting the book
    err = collection.RemoveId(ctx, bookId)

    // to send error response if any error occurs
    if err != nil {
        ctx.JSON(http.StatusInternalServerError, "Something went wrong, Try again after sometime")
        return
    }

    // to send success response on completion
    ctx.JSON(http.StatusOK, true)
}

Enter fullscreen mode Exit fullscreen mode

In the above code block, collection.RemoveId is used to remove the specific data based on the ID provided.

Books List Handler

func GetBooks(ctx *gin.Context) {

    //Getting the Book Data to database
    var books []BookListResponse
    err := collection.Find(ctx, bson.M{}).All(&books)

    // to send error response if any error occurs
    if err != nil {
        fmt.Println(err)
        ctx.JSON(http.StatusInternalServerError, "Something went wrong, Try again after sometime")
        return
    }

    // to send success response on completion
    ctx.JSON(http.StatusOK, books)
}
Enter fullscreen mode Exit fullscreen mode

Here in the List handler, we have used BookListResponse which is used to limit the values read from the database since the id and the name of the book would suffice in the list API. Below is the BookListResponse type.

type BookListResponse struct {
    Id    primitive.ObjectID `json:"id" bson:"_id"` // bson to map mongo _id to id
    Title string             `json:"title"`
}

Enter fullscreen mode Exit fullscreen mode

Here the bson:"_id" is used to map the mongo _id to the ID attribute in the response.

Now all the Handlers have been created let's register the Handlers in the router in main.go by adding the below code block after the router declaration.

    router.GET("/books", GetBooks)
    router.GET("/books/:bookId", GetBook)
    router.PATCH("/books/:bookId", UpdateBook)
    router.DELETE("/books/:bookId", DeleteBook)
Enter fullscreen mode Exit fullscreen mode

Congratulations!!! the CRUD REST API for books in a library using Mongo and Go is successfully completed.

You will be able to find the source code in the Github repo:

GitHub logo Deeshath / books

Books REST API using go and mongo

Books

Books REST API using go and mongo

Pre requisites:

  • An installation of Go 1.20 or later. For installation instructions, visit.
  • An installation of MongoDb 6.0 or later. For installationinstructions, visit

To start thr REST API server use the cmd go run .

Top comments (0)