A TL;DR guide to MVC frameworks

Elijah Omolo
4 min readApr 9, 2022

A Model-View-Controller (MVC) framework is a software architecture pattern that separates an application into three logical parts.

The model handles and maintains data. It connects to the database and is the intermediary between it and the controller.

The view is where data is presented to a user. This would be where the user interface is configured.

The controller handles the interaction between the view and the model. It requests and processes data from the model and sends it to the view with instructions on how to present it.

Our main.go file is getting a bit cluttered as well as there being functions whose logic can be combined. An MVC framework would first help us break the code down into reusable components and then, create a layer of separation between the three logical parts.

Separating the code:

The Model:

First, let’s create the model. This is where the application's dynamic data structure will exist. Essentially, it manages the structure of data. In the userAuth example, this would be the user struct we defined:

// define a user model
type User struct {
Id int
Username string
City string
Email string
Password string
}

Create a models directory where all the model files will be stored. These will be the files that determine the structure of data across the application. Within models, create a file “user.go” where the user struct will be declared:

NOTE: Local packages are a bit of a complex topic in go. To be able to use the models package I had to add the following require statement in the root go.mod file:

github.com/eomolo/user_auth/myapp/models v0.0.0

Then, add a corresponding replace statement:

replace github.com/eomolo/user_auth/myapp/models v0.0.0 => ./models

The Controller:

This is the part of the application that handles interactions between the user and the database. It ensures that the front-end interface will never directly request to or from the database nor will the model ever handle any user requests, errors, or successes.

Create a directory “controllers”, then create a file “user.go” inside it.

Let’s first refactor a bit of our code in the main.go to reduce redundancy and make reusable functions. Notice that the Edir and Show functions are similar:

func Edit(w http.ResponseWriter, r *http.Request) {
db := dbConn()
nId := r.URL.Query().Get("id")
rows, err := db.Query(`SELECT * FROM public."users" WHERE "user_id"=$1`, nId)
CheckError(err)
usr := User{}
for rows.Next() {
var id int
var username, city, email, password string
err = rows.Scan(&id, &username, &city, &email, &password)
CheckError(err)
usr.Id = id
usr.Username = username
usr.Password = password
usr.Email = email
usr.City = city
}
tmpl.ExecuteTemplate(w, "Edit", usr)
//defer db.Close()
}

func Show(w http.ResponseWriter, r *http.Request) {
db := dbConn()
nId := r.URL.Query().Get("id")
rows, err := db.Query(`SELECT * FROM public."users" WHERE "user_id"=$1`, nId)

CheckError(err)
//construct a User
usr := User{}
for rows.Next() {
var id int
var username, city, email, password string
err = rows.Scan(&id, &username, &city, &password, &email)
CheckError(err)
usr.Id = id
usr.Username = username
usr.Email = email
usr.City = city
}
//Execute the Show template using the Users data
tmpl.ExecuteTemplate(w, "Show", usr)
defer db.Close()
}

Both do something similar:

  1. Establish a database connection.
  2. assign a variable to the ID passed in the URL from the HTTP interface
  3. run a query against the db filtering the user_id table using the ID passed from the URL
  4. Handle any errors returned after running the query
  5. If there are no errors. construct a user
  6. Execute a template using the user data
  7. close the database

The only difference in these 2 functions is the template being executed so it’s fair to assume that they can be combined into one function that can then be scaled to use to populate multiple other templates.

Create a function “getUser” that will return an object User defined in the ‘models’ package:

// this needs to return a user or type User
func getUser(id string) models.User {
//connect to database
db := dbConn()
//run a query against the db filtering the user_id table using the passed id
rows, err := db.Query(`SELECT * FROM public."users" WHERE "user_id"=$1`, id)
//handle error
CheckError(err)
//construct a User
usr := models.User{}
for rows.Next() {
var id int
var username, city, email, password string
err = rows.Scan(&id, &username, &city, &password, &email)
CheckError(err)
usr.Id = id
usr.Username = username
usr.Email = email
usr.City = city
}
defer db.Close()
return usr
}

The edit and user functions can now be reduced to the following with the inclusion of the above getUser function that takes the ID passed from the URL as a parameter:

func Edit(w http.ResponseWriter, r *http.Request) {
nId := r.URL.Query().Get("id")
usr := getUser(nId)
tmpl.ExecuteTemplate(w, "Edit", usr)
//defer db.Close()
}

func Show(w http.ResponseWriter, r *http.Request) {
nId := r.URL.Query().Get("id")
usr := getUser(nId)
tmpl.ExecuteTemplate(w, "Show", usr)
}

The functions we previously created to execute CRUD operations should be moved over to controllers/user.go and refactored similarly to this example:

Create an additionally “utils” directory that will contain utility functions such as the database connection:

The Views:

Create a directory “routes” which will be used to map URL patterns and their handlers.

Create a directory “routes” that includes a file “routes.go”. We’ll also be introducing a new package: “github.com/gorilla/mux” that will handle the routing of the URL paths to the application server.

Finally, the main.go should be narrowed down to only running the server:

While there is a bit or refactoring still left to do, this should effectively separate each logical component of the application and be the foundation of its new architecture.

Please let me know if you have any questions, corrections, or suggestions in the comments.

--

--