From Zero to Go Hero: Learn Go Programming with Me - Part 5
Finishing CRUD API Project

Search for a command to run...
Finishing CRUD API Project

No comments yet. Be the first to comment.
Lessons from Code, Content, and Consistency

Reliability basics: Outbox pattern + why it matters

Introducing Kafka/Redpanda + move to event-driven workflow

Connect Orders ↔ Inventory (first working service-to-service flow)

Inventory service + gRPC + proto contracts

Welcome to the last part of this Learn Go Programming series. In the previous parts, we learned lots of new stuff about Go and saw how easy and fast it is to build a backend. In the last blog, we created a Read function for getting all the books that are there in the database.
In this part, we are going to implement the remaining operations which are:
Create Book
Update Book
Delete Book and
Get a Specific Book
So let’s get started without wasting any more time.
Now to create a book, first, let’s configure the route for it.
Go to routes.go and add a new handler called CreateBookHandler
// routes.go
func SetupRouter() *chi.Mux {
...
r.Post("/book", books.CreateBookHandler) // Yet to be created inside handler.go
...
}
handler.go and paste the below code:// handler.go
...
func CreateBookHandler(w http.ResponseWriter, r *http.Request) {
var book Book
err := json.NewDecoder(r.Body).Decode(&book)
if err != nil {
http.Error(w, "Failed to decode book", http.StatusInternalServerError)
return
}
err = CreateBook(book) // Yet to be created inside service.go
if err != nil {
http.Error(w, "Failed to create book", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusCreated)
}
...
So when we are creating something, we are most probably getting the data from the client side. In our case where we are creating a book, let’s say the admin wants to add a book to the website. What he will do is, add the details of the book in text fields on the front side and then press the Submit button.
So when he does that, all the data that were filled by the admin comes to the backend inside a request Body in a JSON format
So what we need to do here is, we just need to decode this data into our Go structure format.
For that, we used this json.Decoder() function which takes json string and then Decode() takes a struct in which we need to fill the converted data.
Then we need to call CreateBook() service of database which will add a new entry for the new book.
Let’s write this function inside service.go
CreateBook Servicefunc CreateBook(book Book) error {
_, err := database.DB.Exec("INSERT INTO books (title, author, year) VALUES (?, ?, ?)", book.Title, book.Author, book.Year)
if err != nil {
return err
}
return nil
}
In service.go file we need to create a function called CreateBook. This function takes a struct of type Book which we will pass from the handler. And it will return an error.
Inside the function, we are writing a create query for MySQL database. It’s self-explanatory so I am not going into details.
In the end, we are returning an error if anything goes wrong.
It was pretty simple, right?
Let’s run the project and test it inside Postman.
routes.go and create a PUT router...
r.Put("/book/{id}", books.UpdateBookHandler)
...
We need an id to update a particular book, right? To do that we can use a unique identifier which is id for our book. So When the user/admin updates a particular book client sends the id of it with the request to our backend.
So if you see, after /book/ we have id inside curly braces. This is a dynamic value. When a client hits a URL with this path like, /book/1 or /book/20, we can get this book dynamic id and perform update operations.
Let’s now create UpdateBook handler function
func UpdateBookHandler(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(r.PathValue("id"))
if err != nil {
http.Error(w, "Failed to convert id", http.StatusInternalServerError)
return
}
var book Book
err = json.NewDecoder(r.Body).Decode(&book)
if err != nil {
http.Error(w, "Failed to decode book", http.StatusInternalServerError)
return
}
err = UpdateBook(id, book)
if err != nil {
http.Error(w, "Failed to update book", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
Okay so in the first line, we are getting id using request objects PathValue function. Here we pass the exact value that we passed inside the route which is id.
But we need to convert them id to int because our database accepts the ID of type integer.
For that, we are using strconv package. And it has Atoi (ASCII to Integer) function which takes a string and converts it to an integer. It also returns err if it isn’t able to convert. So we are handling that error too.
Then we decode the sent JSON data to our book struct.
In the end, we call the UpdateBook a function where we pass the id and book struct.
Let’s create a database service for Updating a book in the database.
UpdateBook Servicefunc UpdateBook(id int, book Book) error {
_, err := database.DB.Exec("UPDATE books SET title = ?, author = ?, year = ? WHERE id = ?", book.Title, book.Author, book.Year, id)
if err != nil {
return err
}
return nil
}
Here we are just executing an update query.
Let’s run the project and see if it’s working.
Okay, so I changed my mind and thought to give you some exercise.
I want you to create DELETE a route for deleting a book. And GET route for getting “a single book“ from the database.
I will share the code with you but before that, I recommend trying it out on your own.
No cheating…
.
.
.
.
.

// routes.go
r.Delete("/book/{id}", books.DeleteBookHandler)
// handler.go
func DeleteBookHandler(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(r.PathValue("id"))
if err != nil {
http.Error(w, "Failed to convert id", http.StatusInternalServerError)
return
}
err = DeleteBook(id)
if err != nil {
http.Error(w, "Failed to delete book", http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
}
//service.go
func DeleteBook(id int) error {
_, err := database.DB.Exec("DELETE FROM books WHERE id = ?", id)
if err != nil {
return err
}
return nil
}
id// routes.go
...
r.Get("/book/{id}", books.GetBookHandler)
...
//handler.go
func GetBookHandler(w http.ResponseWriter, r *http.Request) {
id, err := strconv.Atoi(r.PathValue("id"))
if err != nil {
http.Error(w, "Failed to convert id", http.StatusInternalServerError)
return
}
book, err := GetBook(id)
if err != nil {
http.Error(w, "Failed to fetch book", http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(book)
}
// service.go
func GetBook(id int) (Book, error) {
var book Book
err := database.DB.QueryRow("SELECT id, title, author, year FROM books WHERE id = ?", id).Scan(&book.ID, &book.Title, &book.Author, &book.Year)
if err != nil {
return book, err
}
return book, nil
}
We did finish our project but there are a few things that need to be fixed in this project. For example, When we are Updating a book, we must pass the whole object with every value instead of just a value that we need to update.
There are no clear messages in the console after we perform operations.
We should add proper logs inside each error-handling block.
I want you to do a little bit of research on these points and update the code.
It is pretty easy and I think with a little bit of research and work you will be able to complete it.
That’s it. We have officially completed this series. I hope you learned something from this series.
If you have any queries or concerns regarding any concepts, please feel free to comment it. I’ll try to answer it as per my knowledge.
In future blogs, I am planning to experiment more with Golang and create new projects with it. So stay tuned and follow to get the notifications.
See you in the next one, until then…
