Creating a Simple CRUD (Create-Read-Update-Delete) APP with Go Lang.

Creating a Simple CRUD (Create-Read-Update-Delete) APP with Go Lang.

Prerequisites

  • Have Go installed in your system (Linux/Windows/Mac OS)

  • Have the basic knowledge of Go language, Type literals, Custom types, and basic programming

Introduction

Go is a modern, efficient programming language that is increasingly being used for web development. One of the most common tasks in web development is creating CRUD (Create, Read, Update, Delete) applications, which allow users to create, read, update, and delete data from a database or other data source. In this article, we'll walk through how to create a simple CRUD application with Go language.

Importing necessary Packages

package main

import (
   "encoding/json"
    "fmt"
    "log"
    "net/http"
    "strconv"
    "strings"
)

Creating the data model

I would keep it as simple as possible, The focus here is to see how everything works; Firstly, let's define our data model. For this example, we'll create a simple user model with the following fields:

  • ID

  • First Name

  • Last Name

  • Email

type User struct {
    ID        string `json:"id"`
    FirstName string `json:"first_name"`
    LastName  string `json:"last_name"`
    Email     string `json:"email"`
}

Mocking the database

Here we would be creating a slice of users (with two prefilled data), where we would be fetching our user(s), which will act as our database, but in real applications, based on request or interest, you could choose any database that houses your data but remember that simplicity is the key here.

var users = []User{
    {ID: "1", FirstName: "Aluko", LastName: "Opeyemi", Email: "aluko.ope@gmail.com"},
    {ID: "2", FirstName: "John", LastName: "Doe", Email: "john.doe@example.com"},
}

Creating Our Endpoints

GET /users

The first endpoint we'll create is for retrieving a list of users. This endpoint will simply return a JSON array of all the users in our database (users).

func getUsersHandler(w http.ResponseWriter, r *http.Request) {
    json.NewEncoder(w).Encode(users)
    return
}

GET /users/{id}

The next endpoint we'll create is for retrieving a single user by their ID. This endpoint will take the ID as a URL parameter and return a JSON object representing the user, or a 404 error if the user doesn't exist. We can further tweak our errors and responses to get an optimized result, but let's keep it simple.

func getUserHandler(w http.ResponseWriter, r *http.Request) {
    id := strings.Split(r.URL.Path, "/")[2]
    for _, user := range users {
        if user.ID == id {
            json.NewEncoder(w).Encode(user)
            return
        }
    }
    http.NotFound(w, r)
}

POST /users/create

The fourth endpoint we'll create is for updating an existing user. This endpoint will take the ID of the user to update as a URL parameter and a JSON object representing the updated user in the request body. If the user exists, the endpoint will update the user's fields with the new values and return the updated user, or a 404 error if the user doesn't exist.

func createUserHandler(w http.ResponseWriter, r *http.Request) {
    var user User
    err := json.NewDecoder(r.Body).Decode(&user)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    for _, v := range users {
        if user.Email == v.Email {
            http.Error(w, fmt.Sprintf("user with email %s exist", user.Email), http.StatusNotFound)
            return
        }
    }
    user.ID = strconv.Itoa(len(users) + 1)
    users = append(users, user)
    json.NewEncoder(w).Encode(user)
}

PUT /users/update/{id}

The fourth endpoint we'll create is for updating an existing user. This endpoint will take the ID of the user to update as a URL parameter and a JSON object representing the updated user in the request body. If the user exists, the endpoint will update the user's fields with the new values and return the updated user, or a 404 error if the user doesn't exist.

func updateUserHandler(w http.ResponseWriter, r *http.Request) {
    var user User
    err := json.NewDecoder(r.Body).Decode(&user)
    id := strings.Split(r.URL.Path, "/")[3]
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    for i, u := range users {
        if u.ID == id {
            users[i] = user
            json.NewEncoder(w).Encode(user)
            return
        }
    }
    http.Error(w, fmt.Sprintf("user with ID %s not found", id), http.StatusNotFound)
}

DELETE /users/delete/{id}

The fifth endpoint we'll create is for deleting an existing user. This endpoint will take the ID of the user to update as a URL parameter. If the user exists, the endpoint will delete the user's fields, or return a 404 error if the user doesn't exist.

func deleteUserHandler(w http.ResponseWriter, r *http.Request) {
    id := strings.Split(r.URL.Path, "/")[3]
    fmt.Println(id)
    var user User
    err := json.NewDecoder(r.Body).Decode(&user)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    for i, u := range users {
        if u.ID == id {
            users = append(users[:i], users[i+1:]...)
            json.NewEncoder(w).Encode(user)
            return
        }
    }

    http.Error(w, fmt.Sprintf("user with ID %s not found", id), http.StatusNotFound)
}

Putting it all together

You can go through the whole code if you are missing out anything

package main

import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "strconv"
    "strings"
)

type User struct {
    ID        string `json:"id"`
    FirstName string `json:"first_name"`
    LastName  string `json:"last_name"`
    Email     string `json:"email"`
}

var users = []User{
    {ID: "1", FirstName: "John", LastName: "Doe", Email: "john.doe@example.com"},
    {ID: "2", FirstName: "Jane", LastName: "Doe", Email: "jane.doe@example.com"},
}

func main() {
    http.HandleFunc("/users", getUsersHandler)
    http.HandleFunc("/users/", getUserHandler)
    http.HandleFunc("/users/create", createUserHandler)
    http.HandleFunc("/users/update/", updateUserHandler)
    http.HandleFunc("/users/delete/", deleteUserHandler)
    fmt.Println("Server started at port 8081")
    log.Fatal(http.ListenAndServe(":8081", nil))
}

func getUsersHandler(w http.ResponseWriter, r *http.Request) {
    json.NewEncoder(w).Encode(users)
    return
}

func getUserHandler(w http.ResponseWriter, r *http.Request) {
    id := strings.Split(r.URL.Path, "/")[2]
    for _, user := range users {
        if user.ID == id {
            json.NewEncoder(w).Encode(user)
            return
        }
    }
    http.NotFound(w, r)
}
func createUserHandler(w http.ResponseWriter, r *http.Request) {
    var user User
    err := json.NewDecoder(r.Body).Decode(&user)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    for _, v := range users {
        if user.Email == v.Email {
            http.Error(w, fmt.Sprintf("user with email %s exist", user.Email), http.StatusNotFound)
            return
        }
    }
    user.ID = strconv.Itoa(len(users) + 1)
    users = append(users, user)
    json.NewEncoder(w).Encode(user)
}

func updateUserHandler(w http.ResponseWriter, r *http.Request) {
    var user User
    err := json.NewDecoder(r.Body).Decode(&user)
    id := strings.Split(r.URL.Path, "/")[3]
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }
    for i, u := range users {
        if u.ID == id {
            users[i] = user
            json.NewEncoder(w).Encode(user)
            return
        }
    }

    http.Error(w, fmt.Sprintf("user with ID %s not found", id), http.StatusNotFound)
}

func deleteUserHandler(w http.ResponseWriter, r *http.Request) {
    id := strings.Split(r.URL.Path, "/")[3]
    fmt.Println(id)
    var user User
    err := json.NewDecoder(r.Body).Decode(&user)
    if err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    for i, u := range users {
        if u.ID == id {
            users = append(users[:i], users[i+1:]...)
            json.NewEncoder(w).Encode(user)
            return
        }
    }

    http.Error(w, fmt.Sprintf("user with ID %s not found", id), http.StatusNotFound)
}

Conclusion

The purpose of this article is just to help you get familiar with the go language, and what a simple CRUD application could look like. In real applications, you might want to separate your logic, break your routes into handlers, and maybe separate files. You also won't be storing your users in an array as I did here, but in a database for persistence. There would also be Authorization and Authentication, more concise input validation, choosing best/up-to-date dependencies, and many others.

Thanks for reading, Your questions or contribution are appreciated