Create CRUD Apps with http/net Golang without database
Tags: go, http
Published: 2021-08-22 06:58:48

> 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.

```bash
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
```
Next, please create file main.go, with `touch main.go` inside the project directory.

```go
package main

import "fmt"

func main() {
   fmt.Println("Hello world")
}
```

Than, execute the main.go.

```bash
go run main.go
Hello world # the result
```

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

```go
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))
}
```

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.

```go
// rw is http.ResponseWriter
rw.Write([]byte("Hello world"))
```

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

```go
http.ListenAndServer(":8000", nil)
```

`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.

```bash
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%
```

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`.

```go
 switch r.Method {
    case "GET":
      rw.Write([]byte("GET/ Hello World"))
    case "POST":
      rw.Write([]byte("POST/ Hello World"))
 }
```

Try to access `GET`

```bash
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%
```

Next, try to access `POST`

```bash
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%
```

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

```go
type Todo struct {
	ID   int
	Task string
}

func main() {
...
```

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

```go
type Todo struct {
	ID   int
	Task string
}

var todos []Todo

func main() {
...
```

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

```go
switch r.Method {
case "GET":
  json.NewEncoder(rw).Encode(todos)
...
```

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

Save, and next run `go run main.go` 

```bash
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
```

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

```go
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)
}
```
...next, save and try again, we need to access `POST` to save new value.

```bash
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..."}
```

...next, access GET.

```bash
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..."}]
```

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

```go
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"))
     }
  }
```

...save, and run again

```bash
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..."}]
```

```bash
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%
```

```bash
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

[]
```

```go
 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"))
       }
    }
}
```

...yup, save, and run again

```bash
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..."}]
```

...update data

```bash
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"}]
```

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

```go
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))
}
```

```bash
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
```

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