Web Application to read and write JSON data in JSON File

Golang offers strong support for creating web servers that can serve web pages and web services. Using Golang you can create web servers that can respond to different routes, different types of requests, and different content types. For web development, net/http and html/template are the major packages provided by the standard library. By using these two packages, you can build fully functional web applications.

This is a short example, if you can begin to understand how a web server operates in Golang. By taking reference from this you can create more complex applications. In this example we are going read-write-update JSON data in a JSON file via user interface forms, fetch data for a specific records and also delete data for a specific record.

The net/http standard library package provides multiple methods for creating HTTP servers, and comes with a basic router. The net/http package used here to make requests to server and also to respond to requests.

Our web application is created in the GOPATH location with the folder structure specified as below:

The source file is responsible for initializing routes, starting the index system and starting the web server:

main.go

Example

package main

import (
	handlers "./handlers"
	"net/http"
)

func main() {
	http.HandleFunc("/addnewuser/", handlers.AddNewUserFunc)
	http.HandleFunc("/notsucceded", handlers.NotSucceded)

	http.HandleFunc("/deleted", handlers.DeletedFunc)
	http.HandleFunc("/deleteuser/deleted", handlers.DeleteUserFunc)
	http.HandleFunc("/deleteuser/", handlers.DeleteUserServe)
	http.HandleFunc("/deleteuser/notsuccededdelete", handlers.NotSuccededDelete)

	http.HandleFunc("/", handlers.IndexFunc)

	http.HandleFunc("/showuser/show", handlers.ShowUserFunc)
	http.HandleFunc("/showuser/", handlers.ShowUser)
	http.HandleFunc("/showuser/notsuccededshow/", handlers.NotSuccededShow)

	http.ListenAndServe(":8080", nil)
}

The net/http package is imported. Package http provides HTTP client and server implementations. Within the main function, a route / is created using the HandleFunc method. The HandleFunc registers the handler function for the given pattern. The HandleFunc prevents us from using middleware to perform tasks before and after the execution of our handler. ListenAndServe starts an HTTP server with a given address and handler.


In our application we'll use the JSON data for the communication. Sample testing data:

list.json

Example

[
 {
  "id": 1,
  "firstName": "Mariya",
  "lastName": "Ivanova",
  "balance": 300
 },
 {
  "id": 2,
  "firstName": "EKatina",
  "lastName": "Milevskaya",
  "balance": 5000
 },
 {
  "id": 3,
  "firstName": "Vasek",
  "lastName": "Zalupickiy",
  "balance": 2000
 }
]

JSON in Golang is both write (marshaled) and read (unmarshaled) with the encoding/json package.


The handler package contains various handler functions.

handlers\handlers.go

Example

package handlers

import (
	model "../model"
	"encoding/json"
	"fmt"
	"html/template"
	"io/ioutil"
	"net/http"
	"os"
	"regexp"
	"strconv"
)

//function to check correct user adding input (regular expression and non-empty field input)
func checkFormValue(w http.ResponseWriter, r *http.Request, forms ...string) (res bool, errStr string) {
	for _, form := range forms {
		m, _ := regexp.MatchString("^[a-zA-Z]+$", r.FormValue(form))
		if r.FormValue(form) == "" {
			return false, "All forms must be completed"
		}
		if m == false {
			return false, "Use only english letters if firstname,lastname forms"
		}

	}
	return true, ""
}

func ShowUser(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, "templates/showUser.html")
}

func NotSuccededShow(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, "templates/notSuccededShow.html")

}

//handler to show user with id input
func ShowUserFunc(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" {
		t, _ := template.ParseFiles("templates/showUserPage.html")
		t.Execute(w, nil)

	} else {

		id, err := strconv.Atoi(r.FormValue("id"))
		checkError(err)
		var alUsrs model.AllUsers
		file, err := os.OpenFile("list.json", os.O_RDONLY, 0666)
		checkError(err)
		b, err := ioutil.ReadAll(file)
		checkError(err)
		json.Unmarshal(b, &alUsrs.Users)

		var allID []int
		for _, usr := range alUsrs.Users {
			allID = append(allID, usr.Id)
		}
		for _, usr := range alUsrs.Users {
			if model.IsValueInSlice(allID, id) != true {
				http.Redirect(w, r, "/showuser/notsuccededshow/", 302)
				return
			}
			if usr.Id != id {
				continue
			} else {
				t, err := template.ParseFiles("templates/showUserPage.html")
				checkError(err)
				t.Execute(w, usr)
			}

		}
	}
}

//function to handle page with successful deletion
func DeletedFunc(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, "templates/deleted.html")
}

//serving file with error (add function:empty field input or uncorrect input)
func NotSucceded(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, "templates/notSucceded.html")
}

//function,which serve html file,when deleting was not succesful(id input is not correct)
func NotSuccededDelete(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, "templates/notSuccededDelete.html")
}

//function,which serve page with delete information input
func DeleteUserServe(w http.ResponseWriter, r *http.Request) {
	http.ServeFile(w, r, "templates/deleteUser.html")

}

//function to delete user
func DeleteUserFunc(w http.ResponseWriter, r *http.Request) {
	if r.Method == "GET" {
		t, _ := template.ParseFiles("templates/deleteUser.html")
		t.Execute(w, nil)
	} else {
		r.ParseForm()
		id, err := strconv.Atoi(r.FormValue("id"))
		checkError(err)

		//open file with users
		file, err := os.OpenFile("list.json", os.O_RDWR|os.O_APPEND, 0666)
		defer file.Close()

		//read file and unmarshall json to []users
		b, err := ioutil.ReadAll(file)
		var alUsrs model.AllUsers
		err = json.Unmarshal(b, &alUsrs.Users)
		checkError(err)

		var allID []int
		for _, usr := range alUsrs.Users {
			allID = append(allID, usr.Id)
		}
		for i, usr := range alUsrs.Users {
			if model.IsValueInSlice(allID, id) != true {
				http.Redirect(w, r, "/deleteuser/notsuccededdelete", 302)
				return
			}
			if usr.Id != id {
				continue
			} else {
				alUsrs.Users = append(alUsrs.Users[:i], alUsrs.Users[i+1:]...)
			}

		}
		newUserBytes, err := json.MarshalIndent(&alUsrs.Users, "", " ")
		checkError(err)
		ioutil.WriteFile("list.json", newUserBytes, 0666)
		http.Redirect(w, r, "/deleted", 301)
	}
}

func checkError(err error) {
	if err != nil {
		fmt.Println(err)
	}
}

//function to add user
func AddNewUserFunc(w http.ResponseWriter, r *http.Request) {

	//creating new instance and checking method
	newUser := &model.User{}
	if r.Method == "GET" {
		t, _ := template.ParseFiles("templates/addNewUser.html")
		t.Execute(w, nil)

	} else {
		resBool, errStr := checkFormValue(w, r, "firstname", "lastname")
		if resBool == false {
			t, err := template.ParseFiles("templates/notSucceded.html")
			checkError(err)
			t.Execute(w, errStr)

			return
		}
		newUser.FirstName = r.FormValue("firstname")
		newUser.LastName = r.FormValue("lastname")
		var err error
		newUser.Balance, err = strconv.ParseFloat(r.FormValue("balance"), 64)
		checkError(err)

		//open file
		file, err := os.OpenFile("list.json", os.O_RDWR, 0644)
		checkError(err)
		defer file.Close()

		//read file and unmarshall json file to slice of users
		b, err := ioutil.ReadAll(file)
		var alUsrs model.AllUsers
		err = json.Unmarshal(b, &alUsrs.Users)
		checkError(err)
		max := 0

		//generation of id(last id at the json file+1)
		for _, usr := range alUsrs.Users {
			if usr.Id > max {
				max = usr.Id
			}
		}
		id := max + 1
		newUser.Id = id

		//appending newUser to slice of all Users and rewrite json file
		alUsrs.Users = append(alUsrs.Users, newUser)
		newUserBytes, err := json.MarshalIndent(&alUsrs.Users, "", " ")
		checkError(err)
		ioutil.WriteFile("list.json", newUserBytes, 0666)
		http.Redirect(w, r, "/", 301)

	}

}

//Index page handler
func IndexFunc(w http.ResponseWriter, r *http.Request) {
	au := model.ShowAllUsers()
	t, err := template.ParseFiles("templates/indexPage.html")
	checkError(err)
	t.Execute(w, au)
}

Requests with a /addnewuser path cause the AddNewUserFunc function to be called, requests for /deleted are handled by the DeletedFunc function, requests for /deleteuser are handled by DeleteUserServe and so on. The html/template package parse the HTML template into a Template type and then execute a data structure against that template to create your final output HTML. The main function used to marshal an object is json.Marshal.


The model package contain user Structure and Unmarshaling function.

model\model.go

Example

package model

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
)

func checkError(err error) {
	if err != nil {
		fmt.Println(err)
	}
}

func IsValueInSlice(slice []int, value int) (result bool) {
	for _, n := range slice {
		if n == value {
			return true
		}

	}
	return false

}

type User struct {
	Id        int     `json:"id"`
	FirstName string  `json:"firstName"`
	LastName  string  `json:"lastName"`
	Balance   float64 `json:"balance"`
}

type AllUsers struct {
	Users []*User
}

func ShowAllUsers() (au *AllUsers) {
	file, err := os.OpenFile("list.json", os.O_RDWR|os.O_APPEND, 0666)
	checkError(err)
	b, err := ioutil.ReadAll(file)
	var alUsrs AllUsers
	json.Unmarshal(b, &alUsrs.Users)
	checkError(err)
	return &alUsrs
}

Unmarshaling an object refers to converting JSON into a typed data structure―the reverse of marshaling. In our example, we want to load user data from a list.json file into a User struct.


The Index template is responsible to show list of users and 3 buttons. This template get load to a web path /:

templates\indexPage.html

Example

<html>
<head>
	<title>User info</title>
</head>
<body>

{{range .Users}}
<div><h3> ID:{{ .Id }} </h3></div>
<div> First Name:{{ .FirstName }}</div>
<div> Last Name:{{ .LastName }} </div>
<div> Balance:{{ .Balance }}</div>
{{end}}

<h1>Options</h1>
<form action="/addnewuser/">
    <button type="submit">Add new user</button>
</form>
<form action="/deleteuser/">
    <button type="submit">Delete user</button>
</form>
<form action="/showuser/">
    <button type="submit">Show user</button>
</form>

</body>
</html>


The Adding New User template show the form to enter user data. This template get load to a web path /addnewuser:

templates\addNewUser.html

Example

<html>
<head>
	<title>Adding new user</title>
</head>
<body>
<h2>New user's data</h2>
        <form method="POST" action="useraded">
            <label>Enter firstname</label><br>
            <input type="text" name="firstname" /><br><br>
			<label>Enter lastname</label><br>
            <input type="text" name="lastname" /><br><br>
            <label>Enter balance</label><br>
            <input type="number" name="balance" /><br><br>
            <input type="submit" value="Submit" />
        </form>
</body>
</html>

templates\deleteUser.html

Example

<html>
<head>
	<title>Delete user</title>
</head>
<body>
<h2>Please,write an id of user you want to delete</h2>
        <form method="POST" action="deleted">
            <label>Enter id</label><br>
            <input type="text" name="id" /><br><br>
            <input type="submit" value="Submit" />
        </form>
</body>
</html>

templates\deleted.html

Example

<html>
<head>
	<title>User info</title>
</head>
<body>
	<div>Done</div><br><br>
	<form action="/">
		<button type="submit">Back to main</button>
	</form>
</body>
</html>

templates\notSuccededDelete.html

Example

<html>
<head>
	<title>User info</title>
</head>
<body>
<div>There is no user with such ID</div><br><br>
<form action="/">
    <button type="submit">Back to main</button>
</form>
</body>
</html>

templates\notSuccededShow.html

Example

<html>
<head>
	<title>User info</title>
</head>
<body>
<div>Error:Cant find User with such ID,try again</div><br><br>
<form action="/">
    <button type="submit">Back to main</button>
</form>
</body>
</html>

templates\showUser.html

Example

<html>
<head>
	<title>Show user</title>
</head>
<body>
<h2>Please,write an id of user you want to show</h2>
	<form method="POST" action="show">
		<label>Enter id</label><br>
		<input type="text" name="id" /><br><br>
		<input type="submit" value="Submit" />
	</form>
</body>
</html>

templates\showUserPage.html

Example

<html>
<head>
	<title>User info</title>
</head>
<body>
	<div><h3> ID:{{.Id}} </h3></div>
	<div> First Name:{{ .FirstName }}</div>
	<div> Last Name:{{ .LastName }} </div>
	<div> Balance:{{ .Balance }}</div>
</body>
</html>

templates\notSucceded.html

Example

<html>
<head>
	<title>User info</title>
</head>
<body>
<div>Error:{{.}}</div><br><br>
<form action="/">
    <button type="submit">Back to main</button>
</form>
</body>
</html>

Now launch the application

go run main.go

This will developed a static website on web server on port 8080 on your machine. This also launch your browser navigating to http://localhost:8080.

You will see an output like the image below:

Most Helpful This Week