DEV Community

Azeez Abiodun Solomon
Azeez Abiodun Solomon

Posted on

Build Web App with Go for Beginners Part II

Cover Image

... Thanks for reading the Part I, here's the continuation.

Setting up routes

We're going to setup our routes using mux package as stated here.The following routes would be created just for our app.

  • /: Homepage and listing todo page
  • /add: Add todo page
  • /delete/:id Delete todo action

main.go

package main

import (
    "fmt"
    "github.com/gorilla/mux"
)

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/", controllers.TodoList)
    r.HandleFunc("/add", controllers.TodoAdd)
    r.HandleFunc("/delete/{id}", controllers.TodoDelete)
    http.Handle("/", r)

    fmt.Println("Server started")
    http.ListenAndServe(":3000", nil)
}
Enter fullscreen mode Exit fullscreen mode

Starting the server, You would surely get errors here, but don't worryπŸ˜‰ it's due to the controller package not yet included in the import.

go run main.go
Enter fullscreen mode Exit fullscreen mode

We could decide to create our route functions inside the main.go but for better code structure and readability, we decided to move the functions to a controller package called TodoController.go which is why we have the snippet controllers with references to their appropriate functions in TodoController.go

 controllers.Todo*** 
Enter fullscreen mode Exit fullscreen mode

Setting up Model

For the simplicity of the app, MySQL database is used with GORM
database/connect.go

package database

import (
    "fmt"
    "log"

    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
)

func Init() *gorm.DB {
    user := "root"
    pass := "password"
    host := "localhost"
    dbName := "todo"

    credentials := fmt.Sprintf("%s:%s@tcp(%s:3306)/%s?charset=utf8&parseTime=True&loc=Local", user, pass, host, dbName)

    db, err := gorm.Open("mysql", credentials)

    if err != nil {
        log.Fatal(err)
    }

    return db
}

Enter fullscreen mode Exit fullscreen mode

Here is the structure of our table todo, the SQL version of the Gorm struct model is listed below.

  • id int(10)
  • title varchar(255)
  • completed tinyint(1)
  • created_at datetime
  • updated_at datetime
  • deleted_at datetime

models/Todo.go

package models

import (
    "github.com/jinzhu/gorm"
)

type Todo struct {
    gorm.Model
    Title     string
    Completed bool
}
Enter fullscreen mode Exit fullscreen mode

Handling the logic in the controller

This package is created to handle the logic of the models and views.To render the view, Go has a pre-built package called html/template. We also have;
github.com/iamhabbeboy/todo/database and github.com/iamhabbeboy/todo/models which is the path to both our models and database packages.
controllers/TodoController.go

package controllers

import (
    "fmt"
    "html/template"
    "log"
    "net/http"

    "github.com/iamhabbeboy/todo/database"
    "github.com/iamhabbeboy/todo/models"
)


func TodoList(w http.ResponseWriter, r *http.Request) {
    view, err := template.ParseFiles("views/list.html")
    if err != nil {
        log.Fatal("Template not found ")
    }

    db := database.Init()
    var todos []models.Todo

    query := db.Find(&todos)

    defer query.Close()

    view.Execute(w, todos)
}
Enter fullscreen mode Exit fullscreen mode

views/list.html

<h1>List Todo</h1>
<ul>
    {{ range .}}
        <li>{{ .Title }} <a href="/delete/{{.Model.ID}}" style="color: red">&times;</a> </li></li>
    {{end}}
</ul>

<a href="/add">Add New </a>
Enter fullscreen mode Exit fullscreen mode

Alt Text
We would be using /add route to perform two(2) operations, which are:

  • GET: renders the add page
  • POST: Handles the form request

Which is why we have the switch statement to check the current request.

controllers/TodoController.go

...

func TodoAdd(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case "GET":
        TodoForm(w, r)
    case "POST":
        ProcessTodoForm(w, r)
    }
}

func TodoForm(w http.ResponseWriter, r *http.Request) {
    t, err := template.ParseFiles("views/add.html")
    if err != nil {
        fmt.Println(err)
    }

    t.Execute(w, nil)
}

func ProcessTodoForm(w http.ResponseWriter, r *http.Request) {
    title := r.FormValue("title")
        if title == "" {
        http.Redirect(w, r, "/", 302)
    }
    db := database.Init()
    todo := models.Todo{
        Title:     title,
        Completed: false,
    }
    db.Create(&todo)
    http.Redirect(w, r, "/", 302)
}
Enter fullscreen mode Exit fullscreen mode

views/add.html

  <form method="POST">
        <h1>Todo App</h1>
        <label>Title</label> <br/>
        <input type="text" name="title" required/>
        <br/>
        <input type="submit" value="Add+" />
    </form>
Enter fullscreen mode Exit fullscreen mode

Alt Text

Delete Todo

In other to get todo Id from the /delete route, We have to extract GET params using mux.Vars(r) which requires you to add

import("github.com/gorilla/mux")
Enter fullscreen mode Exit fullscreen mode
...
func TodoDelete(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    id := vars["id"]
    if id == "" {
        http.Redirect(w, r, "/", 302)
    }
    db := database.Init()
    var todos []models.Todo
    db.Where("id = ?", id).Delete(&todos)
    http.Redirect(w, r, "/", 302)
}
Enter fullscreen mode Exit fullscreen mode

Performing the delete action redirects the page back to the homepage that contains list of todos. This is done with http.Redirect(w, r, "/", 302) from import ("net/http").

Thanks for reading, checkout the full source code on github

Top comments (2)

Collapse
 
lewiskori profile image
Lewis kori

awesome post. Really helped. I was struggling with how to structure my projects. Keep up with the good work πŸ‘πŸΎ

Collapse
 
iamhabbeboy profile image
Azeez Abiodun Solomon

I'm glad it helps, Thanks πŸ˜‰