Django Intro with Go and Flamingo

Implementing an MVC App with Flamingo and GORM

Bastian
8 min readFeb 26, 2020
Photo by Gwen Weustink on Unsplash

The Django Framework has a great intro https://docs.djangoproject.com/en/3.0/intro/ where an example poll application is developed.

In this post we want to build a similar app to show how web applications can be developed using the Go based Flamingo Web Framework.

Please note that Flamingo has no automatic admin CRUD generator, so we will skip this parts in this tutorial.

Part 0: Installation

Please make sure to install Go. Version 13 or newer https://golang.org/doc/install

Part 1: Get Started

Creating a new project with Flamingo is actually straight forward.
We start by defining a new Go Module, the bare minimum we need to get started.

~$ mkdir mysite
~$ cd mysite
~/mysite$ go mod init mysite
~/mysite$ go get -v flamingo.me/flamingo/v3

First we create a main.go file to kickstart Flamingo. Beside the normal App we will also add the Flamingo requestlogger Module.

main.go

Now we can run our application with

~/mysite$ go run main.go serve

Now open http://localhost:3322/. Flamingo will give us an json error response because we don’t have anything set up yet.

The Poll app

The Django intro creates a small Poll app, so we will do the very same.

First we place a folder polls in our mysite folder. We start by adding our first view. To be consistent with the Django intro we will name the files in the same way.

We use the same names for files as in the Django intro to be consistent.

We create a views.go and define our first index view.

In Flamingo there is no concept of globals, or global state. Therefore we have to bind everything to structs to make sure flamingo can handle them properly. This will add a few more lines in the beginning, but eventually helps a lot to be more flexible.

polls/view.go

To open the view we need a route. In Flamingo we create a RoutesModule which tells Flamingo how to wire URLs and views together.

polls/urls.go

The first lines are boilerplate code. Essentially we create a new urls struct which gets an instance of our controller. This might seem useless for now but we will need it later.
The Inject method tells Dingo, the dependency injection framework, what the urls depends on.

The Routes method implements the web.RoutesModule interface and configures the RouterRegistry.

Flamingo does not have a configuration like Django where we can “mount” the package on different base-paths. We can, however, change the URLs later in a project using a routes.yml file to rewrite routes.

Last but not least we have to make our Project aware of the routes.

Flamingo uses a module-based approach. We create our module.go and register the URLs:

polls/module.go

In our main.go we will load the module by appending it in our modules list:

main.go

Now we run go run main.go serve and should see our hello world message on http://localhost:3322/.

Part 2: Database Setup

It’s time to add a Database and get ready to define some models.

Flamingo does not bring a default ORM. Instead we use Gorm, a Go ORM Library. We install it via go get -v github.com/jinzhu/gorm in our mysite folder, or just let go download it once we added the imports.

To faciliate this we will create a db package to manage our database setup. The database will be provided by a so-called Provider.

db/module.go

A quick explanation what we are doing: using the Configure method we tell Dingo to bind creation of the gorm.DB instances to a provider.
The provider is called anytime something needs an instance of gorm.DB (e.g. via an Inject method).
Eventually we bind it in a scope, in this case Singleton. The Singleton scope makes sure that we have only one global instance of that database, not one for every controller/action/handler.

As our polls Module will depend on the DB module we define it as a dependency.

polls/module.go

Dingo will now make sure that every time our polls Module is loaded the db Module is loaded prior to that.

Next we define our Models in polls/models.go:

polls/models.go

We want to use Gorm’s AutoMigrate function to handle the database schema. To call it we need to add a CLI command and make our Models available to the DB schema. We gonna use Dingo’s “MultiBinding” concept for that.

In db/module.go we add the CLI Command, and an empty type for models (just so we can reference them):

db/module.go

This code behaves much similar to the database. We bind a new cobra.Command (cobra is a framework for CLI commands) and assign it to a provider.
The provider declares it’s own dependencies (e.g. what the provider needs to run properly) which is the gorm.DB and a list of all registered models.

Actually Gorm could do the AutoMigrate directly in the provider, because it is never destructive. However for this example we will stick to a similar design Django has and want to show how to add new CLI commands, so we keep it like that.

In our polls app we register the models to the command by “Binding” them:

polls/module.go

Now we can setup our Database by running go run main.go migrate. We check our database locally:

~/mysite$ go run main.go migrate
2020-02-25T16:32:27.363+0100 INFO runtime/module.go:32 maxprocs: Leaving GOMAXPROCS=12: CPU quota undefined {"area": "root", "module": "core.runtime", "category": "module"}
2020/02/25 16:32:27 Migrating *polls.Question
2020/02/25 16:32:27 Migrating *polls.Choice
2020-02-25T16:32:27.369+0100 INFO cmd/module.go:83 start graceful shutdown {"area": "root"}
2020-02-25T16:32:27.369+0100 DEBUG zap/module.go:153 Zap Logger shutdown event {"area": "root"}
2020-02-25T16:32:27.369+0100 INFO v3@v3.1.7-0.20200225135826-b18179097bd4/app.go:391 Shutdown server on :3322 {"area": "root"}
2020-02-25T16:32:27.369+0100 INFO cmd/module.go:100 graceful shutdown complete {"area": "root"}

~/mysite$ sqlite3 test.db
SQLite version 3.28.0 2019-04-15 14:49:49
Enter ".help" for usage hints.
sqlite> .schema
CREATE TABLE IF NOT EXISTS "questions" ("id" integer primary key autoincrement,"created_at" datetime,"updated_at" datetime,"deleted_at" datetime,"question_text" varchar(255),"pub_date" datetime );
CREATE TABLE sqlite_sequence(name,seq);
CREATE INDEX idx_questions_deleted_at ON "questions"(deleted_at) ;
CREATE TABLE IF NOT EXISTS "choices" ("id" integer primary key autoincrement,"created_at" datetime,"updated_at" datetime,"deleted_at" datetime,"question_id" integer,"choice_text" varchar(255),"votes" integer );
CREATE INDEX idx_choices_deleted_at ON "choices"(deleted_at) ;

Great! Django would now use the interactive Django CLI. This is a concept which is not really working with Go, as everything is statically compiled.

For demonstration we will add some demo data right via sqlite:

sqlite> INSERT INTO questions (question_text) VALUES ("Question 1"), ("Question 2"), ("Question 3"), ("Question 4"), ("Question 5"), ("Question 6"), ("Question 7");
sqlite> INSERT INTO choices (choice_text, question_id) VALUES ("Choice 1", 6), ("Choice 2", 6), ("Choice 3", 6), ("Yes", 7), ("No", 7), ("Maybe", 7);

Part 3: More Views

We continue by adding more views to inspect our questions.

To do so we first create the views/handler in our views.go.
Also we add a web.Responder to our controller. This is a helper which makes “responding” to web requests much easier.

polls/views.go

We extend our urls.go with the corresponding routes:

polls/urls.go

Restarting (ctrl+c then go run main.go serve again) our application allows us to browse the pages, such as http://localhost:3322/123.

Functionality for views

To show a list of questions we gonna need a template and ask the database to give us available questions.

We start by adding gotemplate.Module to our main.go to add a template engine.

main.go

Afterwards create a template index.html in a folder templates:

templates/index.html

Flamingo does not bundle templates and modules. This is mainly because in most cases templates are highly project specific.

Next we let the index view get a list of questions from the database and show them. To do so we add the gorm.DB as a dependency.

The viewData struct is the data we will pass to the template, and which is available at the .. That make .LatestQuestionList evaluate to the field LatestQuestionList of the viewData struct.

polls/views.go

We can now see the last 5 questions on http://localhost:3322/.

Our responders Render function builds a web.RenderResponse, which renders the given Template (index). The suffix .html can be omitted.

Detail Views and 404

Next we will extend out details view, and handle 404 if the given Question was not found.

polls/views.go

To render out the details we extend the detail.html template:

templates/detail.html

Opening http://localhost:3322/6 or clicking on a question now either gives us the question text, or a 404 message.

Part 4: Forms

Showing Questions is the first step, but we want to interact with the application and be able to vote.

To do so we add a small form to our detail.html template:

templates/detail.html

Next we extend our vote view to handle the submitted post request.

To handle the error cases we use a helper function called noSelectedChoice.

polls/views.go

Now we finish with the result page where we show the votes.

polls/views.go

We add the info to our template in templates/results.html:

templates/results.html

Remove duplicate code

We restructure our polls/views.go a little to remove duplicate code we use to parse question_id and check for the entity in Gorm.
A new method viewDataOrError will try to parse the given ID, check if the entity is available, and either return ready-to-use viewData or a web.Result with a NotFound or ServerError response.

polls/views.go

Part 5: Testing

Most of our functionality is in our views.go so we will add some tests.

In go we use the suffix _test for test files, so we create a new polls/views_test.go file.

Flamingo by default does not use Dingo in tests. We can use it, however it might cause other headaches as we want to focus on tests and not the application in general.

To test/mock our database we add go-sqlmock:

~/mysite$ go get -v github.com/DATA-DOG/go-sqlmock

Our test will set up a test database, then instanciate the controller and Inject the dependencies (web.Responder and gorm.DB).
The empty Responder is fine for testing. We will not render templates here, but we will be able to test if the result of the index() call returns a render response.

Then we call controller.index(context.Background, web.CreateRequest(nil, nil)) to actually “execute” the request.
The context.Background() is used as we are in a test context. The web.CreateRequest method takes two nil arguments, which makes the request create an empty http.Request and a empty web.Session internally.

polls/views_test.go

We can now run our tests with go test ./...:

~/mysite$ go test ./...
? mysite [no test files]
? mysite/db [no test files]
ok mysite/polls 0.348s

Conclusion

Flamingo is not a replacement for Django, and does not try to be one. Django uses the benefits of the interpreted programming language Phyton, for example providing magic features when it comes to models and extending them with lazy-loading method.
On the other hand a compiled programming language like Go has other advantages such as faster runtime and a smaller memory footprint. What this tutorial shows is that it is possible to achieve similar results when it comes to request handling, usage of databases etc. with the Flamingo Framework.

If you want to learn more about Flamingo or support the project check it out on Github and leave a Star :) https://github.com/i-love-flamingo/flamingo

--

--