Example Fullstack CRUD with Go-lang (Go, HTML, Sqlite)

Riris Bayu Asrori
5 min readApr 24, 2021

--

Photo by JJ Ying on Unsplash

Go popularity has increased in recent years as developers love the “speed” that it offers “low” level performance with “high” level syntax make it a good choices for small startup to not having worry of scaling their apps later on the road. On the other hands, for running company some may consider using go for small service that require high performance like making monthly finance report calculating thousand to million rows.

But there is also Rust that offers more or less the same performance as Go, what is good about Go as a programming language? To be honest, the most disappointing thing about rust for me is initial compile time could take hours. If you’re okay with it, Rust also a good choice to use for making a good applications.

Despite of Go-lang as a “low level” language, it comes with complete feature to start making a web app, not like the older language (I’m pointing to C/C++) where we must reinvent “the wheel” every time. There’s already http, template engine, and database connector package from the get go.

Fullstack usually referred to as application that includes both a frontend and a backend. It could be made with separate codebase or one monolithic program that covers everything.

Without further a do, let’s make a simple Go fullstack app.

What we gonna build?

Just a simple todo app (of course), with working CRUD.

There will be 4 actions, with 3 endpoints:

  1. “/” will accept GET list of item and POSTnew Todo item.
  2. “/done/” to flag one item that it has been done.
  3. “/delete/” to delete one item from the list.

Prerequisites

Of course we need to install Go-lang on our computers, here is the official site to install link.

Good Text Editor/IDE is also needed just for the cause of making life not a living hell.

And the main package that we will be using are:

  • GORM (install with go get -u gorm.io/gorm )
  • SQLITE (install with go get -u gorm.io/driver/sqlite )
  • Net/Http (Already available in Go)
  • Html/Template (Already available in Go)

Steps

First, we need to create new directory and initialize go module.

mkdir simple-todo-app
cd simple-todo-app
go mod init simple-todo-app

Create new main.go file in the project directory. We need to import all the necessary package first to the file.

package mainimport (
"fmt"
"strings"
"html/template"
"net/http"
"gorm.io/gorm"
"gorm.io/driver/sqlite"
)
...

Then create new type for data representation on the database table and for our web page. Type Todo is for our database, it required field ID, Title, and Done for flagging and item has been done or not. And TodoPageData is for our html template later.

...
type Todo struct {
gorm.Model
ID uint
Title string
Done bool
}
type TodoPageData struct {
PageTitle string
Todos []Todo
}
...

main function

in our main function, lets connect our app with sqlite database. This will create new “test.db” files on our project directory.

...
func main() {
db, err := gorm.Open(sqlite.Open("test.db"), &gorm.Config{})
if err != nil {
panic("failed to connect database")
}
db.AutoMigrate(&Todo{})
...

The table will automatically follow our Todo type.

Generated Database

For the html template, create new directory named template and create index.html in there.

html template

Inside our index.html add this text:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GO Fullstack</title>
</head>
<body>
<form action="" method="POST">
<input type="text" name="todo"/>
<button type="submit">Save</button>
</form>
<h1>{{.PageTitle}}</h1>
<ul>
{{range .Todos}
{{if .Done}}
<li class="done">{{.Title}} <a href="/delete/{{.ID}}"> Delete</a></li>
{{else}}
<li>{{.Title}} <a href="/done/{{.ID}}">Done</a></li>
{{end}}
{{end}}
</ul>
</body>
</html>

Add new line on our main functions to read index.html

func main() {
...
tmpl := template.Must(template.ParseFiles("template/index.html"))
...

Finally we can create our handler. For “/”, if the request is post, then it will save new item, if not then we will show the list of todo items.

...
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if (r.Method == "POST") {
// Call ParseForm() to parse the raw query and update r.PostForm and r.Form.
if err := r.ParseForm(); err != nil {
fmt.Fprintf(w, "ParseForm() err: %v", err)
return
}
todo := r.FormValue("todo")
db.Create(&Todo{Title: todo, Done: false})
}
//Request not POST
var todos []Todo
db.Find(&todos)
data := TodoPageData{
PageTitle: "My TODO list",
Todos: todos,
}
tmpl.Execute(w, data)
})
...

For “/done/” handler, we need to get the item id but by default http package does not support to read the id from path like “/done/:id”. Of course there’s third party package like Gorilla:Mux. But for the sake of proving my point, lets just use http package, and trim the id using strings package

...
http.HandleFunc("/done/", func(w http.ResponseWriter, r *http.Request) {
id := strings.TrimPrefix(r.URL.Path, "/done/")
var todo Todo
db.First(&todo, id)
todo.Done = true
db.Save(&todo)
http.Redirect(w, r, "/", http.StatusSeeOther)
})
...

“/delete/” handler more or less the same as “/done/”

...
http.HandleFunc("/delete/", func(w http.ResponseWriter, r *http.Request) {
id := strings.TrimPrefix(r.URL.Path, "/delete/")
db.Delete(&Todo{}, id)
http.Redirect(w, r, "/", http.StatusSeeOther)
})
...

And for the last line, let’s serve our app on port 8080

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

Testing

Let’s run our simple app with go run main.go And open your browser then type localhost:8080

Simple Todo App

Add new item , set it to done, and delete it. If anything goes well, congrats on your simple Fullstack Go web app.

--

--