There are a few frameworks for Go that make it easier to build a web Application. Instead of using one of the those frameworks, I wanted to stick with the Go mentality of only using individual packages and build an example model-view-controller (MVC) web application with authentication. Keep in mind, this is just one of the many ways to structure your web application.

All the code and screenshots are available on GitHub: https://github.com/josephspurrier/gowebapp

File Structure

These are the folders and files at the root:

config/ - application settings and database schema static/ - location of statically served files like CSS and JS template/ - HTML templates vendor/app/controller/ - page logic organized by HTTP methods (GET, POST) vendor/app/model/ - database queries vendor/app/route/ - route information and middleware vendor/app/shared/ - packages for templates, MySQL, cryptography, sessions, and json gowebapp.db - SQLite database gowebapp.go - application entry point

External Packages

The external packages I used:

github.com/gorilla/context - registry for global request variables github.com/gorilla/sessions - cookie and filesystem sessions github.com/go-sql-driver/mysql - MySQL driver github.com/haisum/recaptcha - Google reCAPTCHA support github.com/jmoiron/sqlx - MySQL general purpose extensions github.com/josephspurrier/csrfbanana - CSRF protection for gorilla sessions github.com/julienschmidt/httprouter - high performance HTTP request router github.com/justinas/alice - middleware chaining github.com/mattn/go-sqlite3 - SQLite driver golang.org/x/crypto/bcrypt - password hashing algorithm

Application Entry Point

My goal with the main package, gowebapp.go, was to only do a few things:

Define the application settings structs

Read the JSON configuration file and pass those settings to each package

Start the HTTP listener

By using this strategy, it’s easy to add or remove components as you build out your application because all the components are configured in one place.

All the imported packages are either from the standard library or a wrapper.

package main import ( "encoding/json" "log" "os" "runtime" "app/route" "app/shared/database" "app/shared/email" "app/shared/jsonconfig" "app/shared/recaptcha" "app/shared/server" "app/shared/session" "app/shared/view" "app/shared/view/plugin" )

The application settings are defined in the configuration struct and then stored in the config variable.

// config the settings variable var config = &configuration{} // configuration contains the application settings type configuration struct { Database database.Info `json:"Database"` Email email.SMTPInfo `json:"Email"` Recaptcha recaptcha.Info `json:"Recaptcha"` Server server.Server `json:"Server"` Session session.Session `json:"Session"` Template view.Template `json:"Template"` View view.View `json:"View"` } // ParseJSON unmarshals bytes to structs func (c *configuration) ParseJSON(b []byte) error { return json.Unmarshal(b, &c) }

The runtime settings and flags are defined in the init() func. The components are passed the settings from the config.json file in the main() func. The server is then started so the application is accessible via a web browser.

func init() { // Verbose logging with file name and line number log.SetFlags(log.Lshortfile) // Use all CPU cores runtime.GOMAXPROCS(runtime.NumCPU()) } func main() { // Load the configuration file jsonconfig.Load("config"+string(os.PathSeparator)+"config.json", config) // Configure the session cookie store session.Configure(config.Session) // Connect to database database.Connect(config.Database) // Configure the Google reCAPTCHA prior to loading view plugins recaptcha.Configure(config.Recaptcha) // Setup the views view.Configure(config.View) view.LoadTemplates(config.Template.Root, config.Template.Children) view.LoadPlugins( plugin.TagHelper(config.View), plugin.NoEscape(), plugin.PrettyTime(), recaptcha.Plugin()) // Start the listener server.Run(route.LoadHTTP(), route.LoadHTTPS(), config.Server) }

Shared Packages

I wanted the application components to be as decoupled as possible. Each component would be in its own package with a struct that defined its settings. I didn’t want a global registry or a generic container because that creates too many dependencies. I designed it so when the application starts, a single JSON config file is parsed and then passed to a Configure() or Load() func in each package. Many of the packages in the vendor/app/shared/ folder are just wrappers for an external packages. Recently, I decided to move the majority of the Go code to the app folder inside the vendor folder so my own github path is not littered throughout the many imports.

This architecture provides the following benefits:

Each package in the vendor/app/shared/ folder only imports packages from the standard library or an external package so it’s easy to reuse the package in other applications

folder only imports packages from the standard library or an external package so it’s easy to reuse the package in other applications When adding configurable settings to each package, they only need to be added in two places: the config.json file and the package itself

If the API of an external package changes, it’s easy to update the wrapper without modifying any code in your core application

Let’s take a look at the session package step by step.

The package only imports from the standard library and an external package.

package session import ( "net/http" "github.com/gorilla/sessions" )

The struct called Session defines the settings for the package which are readable from a JSON file. A few of the variables are stored in package level variables and are publicly accessible.

var ( // Store is the cookie store Store *sessions.CookieStore // Name is the session name Name string ) // Session stores session level information type Session struct { Options sessions.Options `json:"Options"` // Pulled from: http://www.gorillatoolkit.org/pkg/sessions#Options Name string `json:"Name"` // Name for: http://www.gorillatoolkit.org/pkg/sessions#CookieStore.Get SecretKey string `json:"SecretKey"` // Key for: http://www.gorillatoolkit.org/pkg/sessions#CookieStore.New }

The Configure() func is passed the struct instead of individual parameters so no code needs to be changed outside of the package (except in the JSON file) when the Session struct is changed.

// Configure the session cookie store func Configure(s Session) { Store = sessions.NewCookieStore([]byte(s.SecretKey)) Store.Options = &s.Options Name = s.Name }

The package is used by calling the Instance() func so the core application doesn’t have to reference the gorilla/sessions package directly.

// Session returns a new session, never returns an error func Instance(r *http.Request) *sessions.Session { session, _ := Store.Get(r, Name) return session }

Models

All the models are stored in the vendor/app/model/ folder under one package. The application supports Bolt, MySQL, and MongoDB, but can be easily changed to use another type of database. The example application only has a single model called user.go. The model package is the only place SQL code is written. The file is structured into two parts:

Structs for each table

Funcs for each query

It’s a good idea to make sure the types in the struct match the types in the database.

// User table contains the information for each user type User struct { ObjectID bson.ObjectId `bson:"_id"` ID uint32 `db:"id" bson:"id,omitempty"` // Don't use Id, use UserID() instead for consistency with MongoDB FirstName string `db:"first_name" bson:"first_name"` LastName string `db:"last_name" bson:"last_name"` Email string `db:"email" bson:"email"` Password string `db:"password" bson:"password"` StatusID uint8 `db:"status_id" bson:"status_id"` CreatedAt time.Time `db:"created_at" bson:"created_at"` UpdatedAt time.Time `db:"updated_at" bson:"updated_at"` Deleted uint8 `db:"deleted" bson:"deleted"` } // UserStatus table contains every possible user status (active/inactive) type UserStatus struct { ID uint8 `db:"id" bson:"id"` Status string `db:"status" bson:"status"` CreatedAt time.Time `db:"created_at" bson:"created_at"` UpdatedAt time.Time `db:"updated_at" bson:"updated_at"` Deleted uint8 `db:"deleted" bson:"deleted"` }

The query is stored in a func and clearly named so there is no confusion as to what the query does. To make the app support multiple database types, the code for each database type is contained in each func.

// UserByEmail gets user information from email func UserByEmail(email string) (User, error) { var err error result := User{} switch database.ReadConfig().Type { case database.TypeMySQL: err = database.SQL.Get(&result, "SELECT id, password, status_id, first_name FROM user WHERE email = ? LIMIT 1", email) case database.TypeMongoDB: if database.CheckConnection() { session := database.Mongo.Copy() defer session.Close() c := session.DB(database.ReadConfig().MongoDB.Database).C("user") err = c.Find(bson.M{"email": email}).One(&result) } else { err = ErrUnavailable } case database.TypeBolt: err = database.View("user", email, &result) if err != nil { err = ErrNoResult } default: err = ErrCode } return result, standardizeError(err) } // UserCreate creates user func UserCreate(firstName, lastName, email, password string) error { var err error now := time.Now() switch database.ReadConfig().Type { case database.TypeMySQL: _, err = database.SQL.Exec("INSERT INTO user (first_name, last_name, email, password) VALUES (?,?,?,?)", firstName, lastName, email, password) case database.TypeMongoDB: if database.CheckConnection() { session := database.Mongo.Copy() defer session.Close() c := session.DB(database.ReadConfig().MongoDB.Database).C("user") user := &User{ ObjectID: bson.NewObjectId(), FirstName: firstName, LastName: lastName, Email: email, Password: password, StatusID: 1, CreatedAt: now, UpdatedAt: now, Deleted: 0, } err = c.Insert(user) } else { err = ErrUnavailable } case database.TypeBolt: user := &User{ ObjectID: bson.NewObjectId(), FirstName: firstName, LastName: lastName, Email: email, Password: password, StatusID: 1, CreatedAt: now, UpdatedAt: now, Deleted: 0, } err = database.Update("user", user.Email, &user) default: err = ErrCode } return standardizeError(err) }

Routes

Each of the routes are defined in route.go. I decided to use julienschmidt/httprouter for the speed and then justinas/alice for chaining access control lists (ACLs) to the controller funcs with the main logic for each page. All the middleware is also defined in one place.

I’m going to skip the imports and show how the middleware and routes are combined into HTTP and HTTPS routes. There is also an easy way to redirect HTTP to HTTPS if you would like.

// Load the routes and middleware func Load() http.Handler { return middleware(routes()) } // Load the HTTP routes and middleware func LoadHTTPS() http.Handler { return middleware(routes()) } // Load the HTTPS routes and middleware func LoadHTTP() http.Handler { return middleware(routes()) // Uncomment this and comment out the line above to always redirect to HTTPS //return http.HandlerFunc(redirectToHTTPS) } // Optional method to make it easy to redirect from HTTP to HTTPS func redirectToHTTPS(w http.ResponseWriter, req *http.Request) { http.Redirect(w, req, "https://"+req.Host, http.StatusMovedPermanently) }

I’ll show a few of the routes. The 404 handler and static files are set along with the home page. The different types of HTTP requests like GET and POST are defined separately and each one has an ACL that controls who can access the page.

func routes() *httprouter.Router { r := httprouter.New() // Set 404 handler r.NotFound = alice. New(). ThenFunc(controller.Error404) // Serve static files, no directory browsing r.GET("/static/*filepath", hr.Handler(alice. New(). ThenFunc(controller.Static))) // Home page r.GET("/", hr.Handler(alice. New(). ThenFunc(controller.Index))) // Login r.GET("/login", hr.Handler(alice. New(acl.DisallowAuth). ThenFunc(controller.LoginGET))) r.POST("/login", hr.Handler(alice. New(acl.DisallowAuth). ThenFunc(controller.LoginPOST))) r.GET("/logout", hr.Handler(alice. New(). ThenFunc(controller.Logout))) ... }

The middleware is then added to the passed handler.

func middleware(h http.Handler) http.Handler { // Prevents CSRF and Double Submits cs := csrfbanana.New(h, session.Store, session.Name) cs.FailureHandler(http.HandlerFunc(controller.InvalidToken)) cs.ClearAfterUsage(true) cs.ExcludeRegexPaths([]string{"/static(.*)"}) csrfbanana.TokenLength = 32 csrfbanana.TokenName = "token" csrfbanana.SingleToken = false h = cs // Log every request h = logrequest.Handler(h) // Clear handler for Gorilla Context h = context.ClearHandler(h) return h }

If you’re interested in reading more about the application, take a look at the README and code on GitHub: https://github.com/josephspurrier/gowebapp.

The next article is Go Web App Example - Views, Request Workflow, and View Plugins.

Go Web App Go Web App Example Go Web Application Go Web Application Example Go MVC Web Application Go MVC Web Application Example Golang Web App Golang Web App Example Golang Web Application Golang Web Application Example Golang MVC Web Application Golang MVC Web Application Example