... 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">×</a> </li></li>
{{end}}
</ul>
<a href="/add">Add New </a>
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>
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
Top comments (2)
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 π