Skip to main content

CRUD with Golang

 


Overview

this blog will be about a basic crud app with golang . this app will provide API to store, update, delete information. you need to know the basics of golang to understand this project. if you do not have the basics knowledge about golang I would suggest you to read my previous blog. 

I have uploaded my code in GitHub. click the link to clone or download this project 

I am going to talk about the topic below.

Project setup

 Initiate mod and set up project as per golang. if you don't know how to setup golang project i would suggest you to have a look at my blog  Creating a project with golang. this is a really short blog. you will have a quick over view of how to setup a project with golang. 

so now that you have setup the project then the folder structure will be as below.

Data Handling

data store, delete and update will be handled in the user module. code is given below 


  package moduls

  import (
      "errors"
      "fmt"
  )

  // User struct
  type User struct {
      ID        int
      FirstName string
      LastName  string
  }

  var (
      users  []*User
      nextID = 1
  )

  // GetUsers getting all users memory location
  func GetUsers() []*User {
      return users
  }

  // AddUser add user memory location to users array
  func AddUser(u User) (User, error) {
      if u.ID != 0 {
          return User{}, errors.New("New user must not use ID")
      }
      u.ID = nextID
      nextID++
      users = append(users, &u)

      return u, nil
  }

  func GetUserByID(id int) (User, error) {
      for _, u := range users {
          if u.ID == id {
              return *u, nil
          }
      }

      return User{}, fmt.Errorf("User with ID '%v' not found", id)
  }

  func UpdateUser(u User) (User, error) {
      for i, candidate := range users {
          if candidate.ID == u.ID {
              users[i] = &u
              return u, nil
          }
      }

      return User{}, fmt.Errorf("User with ID '%v' not found", u.ID)
  }

  func RemoveUserbyId(id int) error {
      for i, u := range users {
          if u.ID == id {
              users = append(users[:i], users[i+1:]...)
              return nil
          }
      }

      return fmt.Errorf("user with ID '%v' not found", id)
  }



in this I made 5 functions, 1 struct and 2 variables to handle data. I am going to explain it in 3 part.

just have a look at Functions and Methods of golang for functions in golang, Primitive data type of Golang for variables and Collections of Golang to know about struct. this will give you a glimps of all three topics.

Struct

golang crud user modul (user struct)

this is a struct which can be used as custom data type. we are going to store our user to our computer memory in this format. A user must have an ID which will be automatically incrementing in future and not be taken from user. and the other two will be a type string. if you want to know about struct more then I would suggest you to have a look at my blog about collection in golang.

Variables

golang crud user module variables

 here I have used variable block to declare variable. where I have declared 2 variables. If you want to know more about variables in golang then I would suggest you to have a look at Primitive data type of Golang and List of data types in Golang blog.

User Variable: this variable is a type array and it will take pointers of type User struct. Basically it will be an array of pointers. pointer will be pointing at the the memory address where data is stored of type User struct . if you want to know more about pointers I would suggest you to check out the blog named
Primitive data type of Golang and Pointer in Golang this will give you a look on pointers and how it works and Collections of Golang will give you a small idea on array and basic data structure on golang.

nextID: this variable is declared to set the id for user. as we won't be taking the id from the input so we will be needing a variable where we put our previous id and increment it for future user id.

Functions

for data handling I have used 5 functions, they are 
GetUser: this function is made to get all user which is stored.

golang crud user modul GetUser function

this function will return the array that we have declared in variable scope where the pointers of array will be stored. if this function is called then array will be returned (array of pointers). 

golang crud user modul variables and getUser

the retune type of the function is the type of that users variable which is array of pointer of type User struct. there is some rules and regulation to declare a function. I would suggest you to have look at Functions and Methods of golang blog to know about functions in golang. 

GetUserByID: this function is made to get the single user. it will take the user id and return the user. 

golang crud user modul GetUserByID


the return type of this function is User Struct and error, and the input type of this function is int . this will run a range loop on users array which we declared in variable scope and run if condition so that it can find a match with user id to given id. then it will return u  dereferencing it. details about pointer and dereferencing is given in Pointer in Golang and the details about range loop is given in Controlling Program Flow of golang blog. well we can see if no match found then we have returned empty User{} struct and an error. reason I have returned the empty user is because I have set the return type to User struct and error. And if you can see I have returned nil for error in the condition because my function is expecting the return value of type User struct and error type.

AddUser: this function is made to add user to the user database to users array that we have have declared in variable scope . it will take the data from input and make a variable of type User struct.
and get a memory location of that and store it in the user array which will accept only pointers of type User struct

golang crud user modul AddUser

as you can see this function will take input type of struct User. and it will return a value of type User struct and error. so in this function we are making use of nextID variable that we have declared in variable scope. as you can remember we have initialized the nextID variable to 1, so we are initializing the user id in User information which is given to nextID and incrementing the nextID so that we can use it for next user. and we are appending the new User's memory address to the users array. 

UpdateUser: This function will update user. 

golang crud user modul UpdateUser

it will take a parameter type of  User struct. and will return type of  User struct  and error. it will run a range loop and on users array. and find the index of where the pointer is stored of wanted user. and then replace the address of new user. 

RemoveUser: this will remove user from the users array.

golang crud user modul removeUser

it will take parameter type of int. and return error. and run a range loop and find the index of where it is situated in the array. and make two array from slice of users array. and append it to each other and replaced it to real array which is users array. users[:i] this is the array after i and users[i+1:] array before i  and i is the index where the user is stored which will be removed. if you don't know about slice then I would suggest you to have look at Collections of Golang blog. 

Controller

in controller we will be handballing the http request and make a use of which URL will hit which method and what data will be returned. we will be using built in http package for handling the http request. i will be explaining this  in three topic .

Main

in main.go file I called RegistarController() method which is in controller package. it will be explained in front controller topic. http.ListenAndServe(":3000", nil) is to listen to port 3000. 

    package main

    import (
        "net/http"

        "github.com/TafhimFaisal/basic_crud_with_golang/controller"
    )

    func main() {
        controller.RegistarController()
        http.ListenAndServe(":3000", nil)
    }

Front controller

in ftont.go file in RegistarController() we handle the url. In net/http package Handle() method handles the url. it will have a string for url and a struct of having a particular interface implemented. hear uc is a object of UserController struct which have that particular interface implanted on it. 
newUserController() is constructor method of UserController struct. which will return a pointer of UserController object to avoid duplication of same object.

new newUserController() is a constructor method for UserController struct. we do not have class in golang but we can use struct as a class in golang and there is a way of using contractor method in golang. well in RegistarController we are using the constructor method and in which is in UserController. we will see how we can declare contractor method, make an object and use struct as a class in UserController. you can have basic knowladge about class, method and functions in golang in Functions and Methods of golang blog

Now lets talk about the http.handle() method. it will take the string and listen to port 3000. which we have decaled in main.go file using http.ListenAndServe(":3000", nil) this method of net/http package. and handle() method will take the string and match with url. if it got match then it will redirect it to given UserController's method named ServeHTTP(w http.ResponseWriter, r *http.Request). well from this point we can handle the data using the ServeHTTP(w http.ResponseWriter, r *http.Request)
 method in UserController struct. this method will have input type of http.ResponseWriter and a pointer of http.request. 
To impliement the interface we must have ServeHTTP(w http.ResponseWriter, r *http.Request) method in UserController struct. to get the output. farther we will be talking about this UserController struct in UserController part.

    package controller

    import (
        "encoding/json"
        "io"
        "net/http"
    )

    // RegistarController Registar Controller
    func RegistarController() {
        uc := newUserController()

        http.Handle("/users", *uc)
        http.Handle("/users/", *uc)
    }

    func encodeResponseAsJSON(data interface{}, w io.Writer) {
        enc := json.NewEncoder(w)
        enc.Encode(data)
    }

User controller

in user controller we will be declaring the contractor function and will bind functions to UserController struct and will make sure which URL hits which function.topic that we are going to talk about is :
    package controller

    import (
        "encoding/json"
        "net/http"
        "regexp"
        "strconv"

        "github.com/TafhimFaisal/basic_crud_with_golang/moduls"
    )

    // UserController to controll user
        type UserController struct {
            userIDPattern *regexp.Regexp
    }

    func (uc *UserController) parseRequest(r *http.Request) (moduls.User, error) {
        dec := json.NewDecoder(r.Body)
        var u moduls.User
        err := dec.Decode(&u)

        if err != nil {
            return moduls.User{}, err
        }

        return u, nil
    }

    func newUserController() *UserController {
        return &UserController{
            userIDPattern: regexp.MustCompile(`^/users/(\d+)/?`),
        }
    }

    func (uc UserController) ServeHTTP(w http.ResponseWriter, r *http.Request) {
        if r.URL.Path == "/users" {
            switch r.Method {
            case http.MethodGet:
                uc.getAll(w, r)
            case http.MethodPost:
                uc.post(w, r)
            default:
                w.WriteHeader(http.StatusNotImplemented)
            }
        } else {

            //  w.Write([]byte("hello from user controller !!!"))

            matches := uc.userIDPattern.FindStringSubmatch(r.URL.Path)
            if len(matches) == 0 {
                w.WriteHeader(http.StatusNotFound)
            }

            id, err := strconv.Atoi(matches[1])
            if err != nil {
                w.WriteHeader(http.StatusNotFound)
            }

            switch r.Method {
            case http.MethodGet:
                uc.get(id, w)
            case http.MethodPut:
                uc.put(id, w, r)
            case http.MethodDelete:
                uc.delete(id, w)
            default:
                w.WriteHeader(http.StatusNotFound)
            }
        }
    }

    func (uc *UserController) getAll(w http.ResponseWriter, r *http.Request) {
        // w.Write([]byte("hello from user controller !!!"))
        encodeResponseAsJSON(moduls.GetUsers(), w)
    }

    func (uc *UserController) get(id int, w http.ResponseWriter) {
        u, err := moduls.GetUserByID(id)
        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            return
        }
        encodeResponseAsJSON(u, w)
    }

    func (uc *UserController) post(w http.ResponseWriter, r *http.Request) {
        u, err := uc.parseRequest(r)
        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            return
        }

        u, err = moduls.AddUser(u)
        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            w.Write([]byte(err.Error()))
            return
        }
        encodeResponseAsJSON(u, w)
    }

    func (uc *UserController) put(id int, w http.ResponseWriter, r *http.Request) {
        u, err := uc.parseRequest(r)
        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            w.Write([]byte("could not pars user object"))
            return
        }

        if id != u.ID {
            w.WriteHeader(http.StatusInternalServerError)
            w.Write([]byte("User not found"))
            return
        }

        u, err = moduls.UpdateUser(u)
        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            w.Write([]byte(err.Error()))
            return
        }

        encodeResponseAsJSON(u, w)
    }

    func (uc *UserController) delete(id int, w http.ResponseWriter) {
        err := moduls.RemoveUserbyId(id)
        if err != nil {
            w.WriteHeader(http.StatusInternalServerError)
            w.Write([]byte(err.Error()))
            return
        }
        w.WriteHeader(http.StatusOK)

    }

UserController 

a simple struct which will take a pointer of type regexp.Regexp. in future we are going to use it as class 
and bind method to it. and in previous topic we have returned its object to http.handle() method. 

golang crud userController

Parse Request 

use of this method will be explained in next topic but i will be  explaining what will it do. this method is binded to the struct UserController. So if you don't know how to bind a function to a struct then i would suggest you to have look at my blog Functions and Methods of golang

golang crud user controller parese request

it will take the request and decode the data from body. dec.Decode() method will take a pointer and it will replace the decoded data to that pointer. So we have send a pointer where variable must be a type of User struct (we have talked about user Struct in struct topic of module so if you fail to understand this please go check out that, you will get it.) so if this method can't put the decoded data to the pointer it got. it will return an error. so if it got error it will return empty User and error or else it will return the user that dec.Decode() replaced the value

NewUserController

this is the constructor method for UserController struct. where we return the object of struct. one of the reason why we use constructor that to have a premade object that will have a specific characteristics to it. so this method dose exactly that but in a different way. 

golang crud user controller newusercontroller

it will make a object of UserController and return the pointer of that of having a userIDPattern of regexp.MustCompile(`^/users/(\d+)/?`).
so when we call this method. we will get a pointer of premade UserController struct.

ServeHTTP

this is the most important method that we have binded to the struct so far. remember we passed the obejct of userController struct in previously in  front controller in http.Handle("/users", *uc) like this. for that to work we need to have this method bind in userController. http.Handle("/users", *uc) will handle that request and we can handle the data as we want from this method. because we have send this struct to http.Handle("/users", *uc). it will use the ServeHTTP method form the userController.

golang crud user controller ServeHTTP


in this method we will be handling which URL hits which method. So the code is pretty much self explanatory. but in this method we have not also bind the methods to UserController struct but also used the method that is in the UserController struct. the problem with the golang is. there is no this key word. so we have to use the method that is in same struct in the way we bind it.

the method that i have used in this method is explained below:

getAll 

    this will return all the users that already existed in the data base. you can see I have used moduls.GetUsers() method. I made that in module package i have explained it on functions part.

golang crud user controller getAll

so if you can see in the picture below. getAll() method gets the return of GetUsers() function and ecodeResponse in json. 

golang crud user modul user controller getusers

get

this method will take an id and get the single user. code is pretty much self explanatory. the explanation of GetUserByID() is given in GetUserByID section. 

golang crud user controller get method


user controller user modul get user by id

Post

this will add user information and store data to the array we have declared . it uses AddUser()  function store data.

golang crud user controller post


user controller user modul add user


Put

this method will update the user data. this will use the UpdateUser() which is in module package.

golang crud user controller put


user controller user modul update user


Delete

this will remove the user from the array. it will use RemoveUserId() in module package.

golang crud user controller delete


user controller user modul remove user



Comments

Popular posts from this blog

Primitive data type of Golang

Overview In this blog I am going to talk about the date types and variables in go. and the topics bellow  Declaring and initializing variables   explicit initialization of variables   implicit initialization of variables complex data type list of data type Pointer pointer operator dereferencing operator address of operator Constant Iota and constant expression Declaring and initializing variables there are 2 ways you can declarer and initialize a variable they are explicit initialization variables  implicit initialization variables  Explicit initialization of variables  in this way you have to tell the compiler every thing you need to declare a variable. var i int i = 20 var j int = 3 fmt.Println(i,j) var f float32 = 32.233 fmt.Println(f)  in this way we are explicitly telling what will be the input type for that variable. Implicit initialization of variables  in this way of declaring a variable. we don't have to tell the compiler what type of data it will take on that

Creating a project with Golang

Overview    You can run a go file with out making a project. But the standard way to use go is to create a project or you can call it a space where you will be putting your go code, on the other hand it is a work space, but in Go Lang we call it module. In Go Lang modules are the official way of organize go code. in this blog I am going to explain the way you create a model and first hello world with that project. Explanation To Module you need to run the command bellow go mod init <module name>  If you can see I have named my module github.com/TahimFaisal/golang. the name of the module is called module initializer. this is the standard way of initialization. And for the go get command it will it will know where to go and get the mod if it cannot find the mod locally.  Okay, soon after you ran the command you will get a file named go.mod in your working directory  it will be having module initializer and the version of go your using.     now create a main.go file and write