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:
Design API endpoints.
Create a folder for code.
Connect to MongoDB using qmgo
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
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
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.
packagemainfuncmain(){}
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.
vardatabase*qmgo.Databasevarcollection*qmgo.Collectionfuncmain(){// create new ClientconstdatabaseURI="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 connectioncollection=database.Collection("books")// get the collectiondeferfunc(){iferr=connection.Close(ctx);err!=nil{fmt.Println("Closing Connection to database",databaseURI)panic(err)}}()}
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 routesrouter.POST("/books",CreateBook)fmt.Println("Service is up & running at localhost:8000")router.Run(":8000")// register router to port 8000
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 requiredtypeBookCreateUpdateRequeststruct{Titlestring`form:"title" binding:"required"`Authorstring`form:"author"`}// json:"id" to map the struct Name to its Json field nametypeBookResponsestruct{Idprimitive.ObjectID`json:"id"`Titlestring`json:"title"`Authorstring`json:"author"`CreatedAttime.Time`json:"createdAt"`UpdatedAttime.Time`json:"updatedAt"`}
Let's create the CreateBookmethod to handle book creation requests.
funcCreateBook(ctx*gin.Context){varnewBookBookCreateUpdateRequest// to bind the received JSON to BookRequest to strip the unnecessary fields.iferr:=ctx.ShouldBind(&newBook);err!=nil{ctx.JSON(http.StatusBadRequest,"Invalid Request")return}// setting data to book model structbook:=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 occursiferr!=nil{ctx.JSON(http.StatusInternalServerError,"Something went wrong, Try again after sometime")return}// to send success response on completionctx.JSON(http.StatusCreated,GetBooksResponse(book))}funcGetBooksResponse(bookBook)(bookResponseBookResponse){// setting response for bookbookResponse=BookResponse{Id:book.DefaultField.Id,Title:book.Title,Author:book.Author,CreatedAt:book.CreateAt,UpdatedAt:book.UpdateAt,}return}
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:
funcGetBook(ctx*gin.Context){// to get and convert the received path variable to desired typebookId,err:=primitive.ObjectIDFromHex(ctx.Param("bookId"))iferr!=nil{ctx.JSON(http.StatusBadRequest,"Invalid Request")return}//Getting the Book Data from databasevarbookBookerr=collection.Find(ctx,bson.M{"_id":bookId}).One(&book)// to send error response if any error occursiferr!=nil{ctx.JSON(http.StatusNotFound,"Book Not Found")return}// to send success response on completionctx.JSON(http.StatusOK,GetBooksResponse(book))}
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:
funcUpdateBook(ctx*gin.Context){// to get and convert the received path variable to desired typebookId,err:=primitive.ObjectIDFromHex(ctx.Param("bookId"))iferr!=nil{ctx.JSON(http.StatusBadRequest,"Invalid Book ID")return}varnewBookBookCreateUpdateRequest// to bind the received JSON to BookRequest to strip the unnecessary fields.iferr:=ctx.ShouldBind(&newBook);err!=nil{ctx.JSON(http.StatusBadRequest,"Invalid Request")return}//Getting the Book Data from databasevarbookBookerr=collection.Find(ctx,bson.M{"_id":bookId}).One(&book)// to send error response if any error occursiferr!=nil{ctx.JSON(http.StatusNotFound,"Book Not Found")return}// set the updated value in the bookbook.Author=newBook.Authorbook.Title=newBook.Title// update in databaseerr=collection.ReplaceOne(ctx,bson.M{"_id":bookId},&book)iferr!=nil{ctx.JSON(http.StatusInternalServerError,"Something went wrong, Try again after sometime")return}// to send success response on completionctx.JSON(http.StatusOK,GetBooksResponse(book))}
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:
funcDeleteBook(ctx*gin.Context){// to get and convert the received path variable to desired typebookId,err:=primitive.ObjectIDFromHex(ctx.Param("bookId"))iferr!=nil{ctx.JSON(http.StatusBadRequest,"Invalid Request")return}//Getting the Book Data from databasevarbookBookerr=collection.Find(ctx,bson.M{"_id":bookId}).One(&book)// to send error response if any error occursiferr!=nil{ctx.JSON(http.StatusNotFound,"Book Not Found")return}// Deleting the bookerr=collection.RemoveId(ctx,bookId)// to send error response if any error occursiferr!=nil{ctx.JSON(http.StatusInternalServerError,"Something went wrong, Try again after sometime")return}// to send success response on completionctx.JSON(http.StatusOK,true)}
In the above code block, collection.RemoveId is used to remove the specific data based on the ID provided.
Books List Handler
funcGetBooks(ctx*gin.Context){//Getting the Book Data to databasevarbooks[]BookListResponseerr:=collection.Find(ctx,bson.M{}).All(&books)// to send error response if any error occursiferr!=nil{fmt.Println(err)ctx.JSON(http.StatusInternalServerError,"Something went wrong, Try again after sometime")return}// to send success response on completionctx.JSON(http.StatusOK,books)}
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.
typeBookListResponsestruct{Idprimitive.ObjectID`json:"id" bson:"_id"`// bson to map mongo _id to idTitlestring`json:"title"`}
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.