loading...

Build Web App with Go for Beginners Part II

iamhabbeboy profile image Azeez Abiodun Solomon ・3 min read

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

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

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

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
}

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
}

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

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>

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

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>

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")
...
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)
}

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

Posted on by:

iamhabbeboy profile

Azeez Abiodun Solomon

@iamhabbeboy

A software developer that derive joy in creating stuffs and helping others

Discussion

markdown guide
 

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

 

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