DEV Community

Adiatma Kamaarudin
Adiatma Kamaarudin

Posted on

Create CRUD Apps with http/net Golang without database

This article just have a purpose to show how to create CRUD (Create, Read, Update & Delete) Apps just using http/net.

First step we need to create project directory.

mkdir golang-basic-http # create directory project
cd golang-basic-http # go to project
go mod init golang-basic-http # initialize go module inside the project
Enter fullscreen mode Exit fullscreen mode

Next, please create file main.go, with touch main.go inside the project directory.

package main

import "fmt"

func main() {
   fmt.Println("Hello world")
}
Enter fullscreen mode Exit fullscreen mode

Than, execute the main.go.

go run main.go
Hello world # the result
Enter fullscreen mode Exit fullscreen mode

Next, we need to import http/net and create the http server.

package main

import (
    "log"
    "net/http"
)

func main() {
    http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
        rw.Write([]byte("Hello World"))
    })

    log.Fatal(http.ListenAndServe(":8000", nil))
}
Enter fullscreen mode Exit fullscreen mode

Ok, http.HandleFunc have 2 parameters, to fill with route path and handler, path describe the route, and handler is a function, and the function have 2 parameters http.ResponseWriter to write the output, and http.Request to catch the request from http.

Next, inside handler we need to create response Hello World if user access the path.

// rw is http.ResponseWriter
rw.Write([]byte("Hello world"))
Enter fullscreen mode Exit fullscreen mode

And to listens on the TCP network we using http.ListenAndServer.

http.ListenAndServer(":8000", nil)
Enter fullscreen mode Exit fullscreen mode

http.ListenAndServer have 2 parameters, first parameter fill with the port address, and next with route config, because we don't use router configuration, for this time, second parameter in http.ListenAndServer we can fill with nil.

If you access the http with port 8000 using curl in the terminal.

curl -i http://localhost:8000

HTTP/1.1 200 OK
Date: Sun, 22 Aug 2021 04:58:38 GMT
Content-Length: 11
Content-Type: text/plain; charset=utf-8

Hello world%
Enter fullscreen mode Exit fullscreen mode

Yes, congratulations.

Next, we need create methods GET, POST, DELETE, and PUT inside the handler.

To get methods, we need access r in http.Request.

 switch r.Method {
    case "GET":
      rw.Write([]byte("GET/ Hello World"))
    case "POST":
      rw.Write([]byte("POST/ Hello World"))
 }
Enter fullscreen mode Exit fullscreen mode

Try to access GET

curl -i http://localhost:8000

HTTP/1.1 200 OK
Date: Sun, 22 Aug 2021 05:26:01 GMT
Content-Length: 16
Content-Type: text/plain; charset=utf-8

GET/ Hello World%
Enter fullscreen mode Exit fullscreen mode

Next, try to access POST

curl -i -X POST http://localhost:8000

HTTP/1.1 200 OK
Date: Sun, 22 Aug 2021 05:25:44 GMT
Content-Length: 17
Content-Type: text/plain; charset=utf-8

POST/ Hello World%
Enter fullscreen mode Exit fullscreen mode

Ok, next we need to define data, example Todo with have Task and ID.

type Todo struct {
    ID   int
    Task string
}

func main() {
...
Enter fullscreen mode Exit fullscreen mode

...and, define collection of Todo, in todos variable.

type Todo struct {
    ID   int
    Task string
}

var todos []Todo

func main() {
...
Enter fullscreen mode Exit fullscreen mode

...next we need to access todos, inside handler.

switch r.Method {
case "GET":
  json.NewEncoder(rw).Encode(todos)
...
Enter fullscreen mode Exit fullscreen mode

json.NewEncoder(rw).Encode(todos) convert todos and encode to json format as the result.

Save, and next run go run main.go

HTTP/1.1 200 OK
Date: Sun, 22 Aug 2021 05:54:23 GMT
Content-Length: 5
Content-Type: text/plain; charset=utf-8

null
Enter fullscreen mode Exit fullscreen mode

Oops, the result is null cause todos is empty, we need to create post to fill todos with new value.

case "POST":
   // create variable to save todo
   var todo Todo
   // decode `r.Body` `r *http.Request` to get data from request
   // decode the result to todo
   json.NewDecoder(r.Body).Decode(&todo)
   // append new todo in todos
   todos = append(todos, todo)
   // finally, encode again create the resutl with json format
   json.NewEncoder(rw).Encode(todo)
}
Enter fullscreen mode Exit fullscreen mode

...next, save and try again, we need to access POST to save new value.

curl -i -X POST -d '{"ID": 1, "Task": "Coding..."}' http://localhost:8000

HTTP/1.1 200 OK
Date: Sun, 22 Aug 2021 06:01:37 GMT
Content-Length: 28
Content-Type: text/plain; charset=utf-8

{"ID":1,"Task":"Coding..."}
Enter fullscreen mode Exit fullscreen mode

...next, access GET.

curl -i http://localhost:8000

HTTP/1.1 200 OK
Date: Sun, 22 Aug 2021 06:01:59 GMT
Content-Length: 30
Content-Type: text/plain; charset=utf-8

[{"ID":1,"Task":"Coding..."}]
Enter fullscreen mode Exit fullscreen mode

Next, we need add method DELETE to remove todo base on the ID in todo.

case "DELETE":
  // http/net not support path variable like this `/:id`
  // and to handle it, we using `r.URL.Query` to get the query url
  // http://localhost:8000?id=1
  query := r.URL.Query()
  // id result have a type string
  // we need `strconv.Atoi` to convert string into int
  id, _ := strconv.Atoi(query.Get("id"))

  // next we need to loop todos, and match the id with todo.ID
  for index, todo := range todos {
     if todo.ID == id {
        // splice and replace the todo
        todos = append(todos[:index], todos[index+1:]...)
        rw.Write([]byte("Success to deleted todo"))
     }
  }
Enter fullscreen mode Exit fullscreen mode

...save, and run again

curl -i http://localhost:8000

HTTP/1.1 200 OK
Date: Sun, 22 Aug 2021 06:25:21 GMT
Content-Length: 30
Content-Type: text/plain; charset=utf-8

[{"ID":1,"Task":"Coding..."}]
Enter fullscreen mode Exit fullscreen mode
curl -i -X DELETE http://localhost:8000\?id\=1

HTTP/1.1 200 OK
Date: Sun, 22 Aug 2021 06:26:30 GMT
Content-Length: 23
Content-Type: text/plain; charset=utf-8

Success to deleted todo%
Enter fullscreen mode Exit fullscreen mode
curl -i http://localhost:8000

HTTP/1.1 200 OK
Date: Sun, 22 Aug 2021 06:27:00 GMT
Content-Length: 3
Content-Type: text/plain; charset=utf-8

[]
Enter fullscreen mode Exit fullscreen mode
 case "PUT":
    query := r.URL.Query()
    id, _ := strconv.Atoi(query.Get("id"))

    for index, todo := range todos {
       json.NewDecoder(r.Body).Decode(&todo)

       if todo.ID == id {
          todos[index].ID = todo.ID
          todos[index].Task = todo.Task

          rw.Write([]byte("Success to update todo"))
       }
    }
}
Enter fullscreen mode Exit fullscreen mode

...yup, save, and run again

curl -i -X POST -d '{"ID": 1, "Task": "Coding..."}' http://localhost:8000

HTTP/1.1 200 OK
Date: Sun, 22 Aug 2021 06:33:35 GMT
Content-Length: 28
Content-Type: text/plain; charset=utf-8

{"ID":1,"Task":"Coding..."}

---
curl -i http://localhost:8000

HTTP/1.1 200 OK
Date: Sun, 22 Aug 2021 06:34:10 GMT
Content-Length: 30
Content-Type: text/plain; charset=utf-8

[{"ID":1,"Task":"Coding..."}]
Enter fullscreen mode Exit fullscreen mode

...update data

curl -i -X PUT -d '{"ID": 1, "Task": "Bobo siang"}' http://localhost:8000\?id\=1

HTTP/1.1 200 OK
Date: Sun, 22 Aug 2021 06:35:32 GMT
Content-Length: 22
Content-Type: text/plain; charset=utf-8

Success to update todo%

---
curl -i http://localhost:8000

HTTP/1.1 200 OK
Date: Sun, 22 Aug 2021 06:39:24 GMT
Content-Length: 35
Content-Type: text/plain; charset=utf-8

[{"ID": 1, "Task": "Bobo siang"}]
Enter fullscreen mode Exit fullscreen mode

Yes, congratulations!, and next we need to custom header, and need a little refactor.

package main

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

type Todo struct {
    ID   int
    Task string
}

var todos []Todo

func main() {
    http.HandleFunc("/", func(rw http.ResponseWriter, r *http.Request) {
        rw.Header().Add("Content-Type", "application/json")
        rw.Header().Add("Access-Control-Allow-Origin", "*")

        query := r.URL.Query()
        id, _ := strconv.Atoi(query.Get("id"))

        switch r.Method {
        case "GET":
            rw.WriteHeader(http.StatusOK)
            json.NewEncoder(rw).Encode(todos)
        case "POST":
            var todo Todo
            json.NewDecoder(r.Body).Decode(&todo)
            todos = append(todos, todo)
            rw.WriteHeader(http.StatusOK)
            json.NewEncoder(rw).Encode(todo)
        case "DELETE":
            for index, todo := range todos {
                if todo.ID == id {
                    todos = append(todos[:index], todos[index+1:]...)
                    rw.WriteHeader(http.StatusOK)
                    rw.Write([]byte(`{"message": "Success to delete todo"}`))
                }
            }
        case "PUT":
            for index, todo := range todos {
                if todo.ID == id {
                    json.NewDecoder(r.Body).Decode(&todo)
                    todos[index].ID = todo.ID
                    todos[index].Task = todo.Task
                    rw.WriteHeader(http.StatusOK)
                    rw.Write([]byte(`{"message": "Success to update todo"}`))
                }
            }
        }
    })

    log.Fatal(http.ListenAndServe(":8000", nil))
}
Enter fullscreen mode Exit fullscreen mode
curl http://localhost:8000 -v

*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8000 (#0)
> GET / HTTP/1.1
> Host: localhost:8000
> User-Agent: curl/7.64.1
> Accept: */*
>
< HTTP/1.1 200 OK
< Access-Control-Allow-Origin: *
< Content-Type: application/json
< Date: Sun, 22 Aug 2021 06:54:01 GMT
< Content-Length: 5
<
null
* Connection #0 to host localhost left intact
* Closing connection 0
Enter fullscreen mode Exit fullscreen mode

Yes, hopefully you can learn something with this basic article about http server in golang.

Top comments (1)

Collapse
 
dev_en profile image
Deven1902

hey great article... helps a lot!