MVC in Go
Go programming language (Golang) is a high performance programming language. It’s developed by experts working in Google. Go is powerful yet beautiful language that worth to learn, especially if you have experience in C or PHP. In short, Go makes programming fun again.
In this short post, I will show some code snippets to give overview of how to program in Go using Model View and Controller (MVC) pattern.
Firstly, it is important to know the flow of HTTP request and response.
Here is the flow of request:
Browser -> Request -> Route -> (Middleware/opt)-> Controller -> Model -> Database Query
And here’s the flow of response:
Query Result -> Model -> Controller -> View -> Response -> Browser
Next, we will start exploring the backend code.
For example, we will create a simple Notes App.
First, we want to read our note list at http://localhost/notepad. So we design our route as follow:
r.GET("/notepad", hr.Handler(alice.
New(acl.DisallowAnon).
ThenFunc(controller.NotepadReadGET)))
So if our app receive a request at this URL, then we will call a acl.DisallowAnon middleware and then call controller.NotepadReadGET. DisallowAnon function within the acl package is a middleware that checks user session. If the user has logged in, then user will be allowed to continue the process (go to NotepadReadGET method), otherwise, user will not be able to access the page.
// DisallowAnon does not allow anonymous users to access the page
func DisallowAnon(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Get session
sess := session.Instance(r)
// If user is not authenticated, don't allow them to access the page
if sess.Values["id"] == nil {
http.Redirect(w, r, "/", http.StatusFound)
return
}
h.ServeHTTP(w, r)
})
}
Upon successful, the app will call a NotepadReadGET controller.
// NotepadReadGET displays the notes in the notepad
func NotepadReadGET(w http.ResponseWriter, r *http.Request) {
// Get session
sess := session.Instance(r)
userID := fmt.Sprintf("%s", sess.Values["id"])
notes, err := model.NotesByUserID(userID)
if err != nil {
log.Println(err)
notes = []model.Note{}
}
// Display the view
v := view.New(r)
v.Name = "notepad/read"
v.Vars["first_name"] = sess.Values["first_name"]
v.Vars["notes"] = notes
v.Render(w)
}
This controller call another function in model package, named model.NotesByUserID(userID). This function takes userID as its parameter. Here’s the code of NotesByUserID
// NotesByUserID gets all notes for a user
func NotesByUserID(userID string) ([]Note, error) {
var err error
var result []Note
err = database.SQL.Select(&result, "SELECT id, content, user_id, created_at, updated_at, deleted FROM note WHERE user_id = ?", userID)
return result, standardizeError(err)
}
This model will execute a query selecting all notes of this user and return the results and the error message (if any).
Next, the result will be served as HTML page using notepad/read template:
<h1>{{.first_name}}'s Notepad</h1>
</div>
<p>
<a title="Add Note" class="btn btn-primary" role="button" href="{{$.BaseURI}}notepad/create">
<span class="fa fa-plus" aria-hidden="true"></span> Add Note
</a>
</p>
{{range $n := .notes}}
<div class="panel panel-default">
<div class="panel-body">
<p>{{.Content}}</p>
<div style="display: inline-block;">
<a title="Edit Note" class="btn btn-warning" role="button" href="{{$.BaseURI}}notepad/update/{{.NoteID}}">
<span class="fa fa-edit" aria-hidden="true"></span> Edit
</a>
<a title="Delete Note" class="btn btn-danger" role="button" href="{{$.BaseURI}}notepad/delete/{{.NoteID}}">
<span class="fa fa-trash" aria-hidden="true"></span> Delete
</a>
</div>
<span class="pull-right" style="margin-top: 14px;">{{.UpdatedAt | PRETTYTIME}}</span>
</div>
</div>
{{end}}
Here’s the result