Building Web Applications With Beego

Developing a web application with Beego - Part 2

Welcome back to part 2 of the series, where we get up to speed with the Beego web development framework for Go. If you missed part 1, I encourage you to read it, as it lays the foundation for this series.

In part 1, we made a good start, understanding and working with Beego by installing Beego and the command line tool Bee, creating a basic project, adding a controller action, creating a basic view template, adding a custom route and finished up learning how to work with request parameters.

In part 2, we’ll be getting into more of the fun aspects of building a web application by integrating a database, specifically with SQLite3, as well as looking at models, forms and validation. I hope you are ready to go, as this is going to be a good ride through to the end.

2-Step Views

You’ll notice throughout a number of the functions in the manage controller, the following code:

manage.Layout = "basic-layout.tpl"
manage.LayoutSections = make(map[string]string)
manage.LayoutSections["Header"] = "header.tpl"
manage.LayoutSections["Footer"] = "footer.tpl"

What this does is set up a 2-step view layout. If you’re not familiar with the term, it’s where you have an outer layout which is always displayed, such as sidebars, navigation, headers and footers and inner content which changes based on the action being executed.

2 Step View Layout

The image above illustrates what I mean. The green areas are in the outer, wrapper view and the red is the content which changes, based on the executing action.

By referencing Layout and LayoutSections, we’re able to specify the outer layout view template, basic-layout.tpl, and other sub-templates, in our case a header and footer, available in header.tpl and footer.tpl respectively.

By doing this the content generated by our action template will be inserted into the wrapping view template, by specifying {{.LayoutContent}} and header and footer will be available in {{.Header}} and {{.Footer}} respectively.


To add database support, we need to do a few things. Firstly, we need to set up some models. Models are basically just structs with some additional information. Below is the model file, which you’ll find in models/models.go, which we’ll use throughout the rest of the application.

package models

type Article struct {
    Id     int    `form:"-"`
    Name   string `form:"name,text,name:" valid:"MinSize(5);MaxSize(20)"`
    Client string `form:"client,text,client:"`
    Url    string `form:"url,text,url:"`

func (a *Article) TableName() string {
    return "articles"

You can see there’s one model, Article, which models a very simple website article and contains four properties: Id, Name, Client and Url. What you’ll notice is that for each property, there is additional data, mentioning form and valid.

This is a really simple way of being able to use the model to handle both form generation as well as form validation. Let’s work through each of the four properties now and explain what each does.

Id int `form:"-"`

In our database, id is an auto-increment field. This works nicely as the value should be created for us if it’s a new record, only being supplied when deleting, updating or searching for the record. So by specifying form:"-", what we’re saying is that Id is not required.

Name   string `form:"name,text,name:" valid:"MinSize(5);MaxSize(20)"`

Here we have a slightly more complex example, so let’s break it down, starting with "name,text,name:". This means that when the form is parsed, which we’ll see shortly:

  • The value from the form field with the name name will initialize the Name property with
  • The field will be a text field
  • The label will be set to ‘name:’

Now let’s look at valid:"MinSize(5);MaxSize(20)". This specifies two validation rules: MinSize and MaxSize. In effect, the value supplied must be at least 5 characters long but no longer than 20.

There are a number of other validation rules which you can use, including Range, Email, IP, Mobile, Base64 and Phone.

Client string `form:"client,text,client:"`
Url    string `form:"url,text,url:"`

In these last two examples, Client will take its value from the form field client, be a text field and have the label client: and Url will take its value from the form field url, be a text field and have the label url:. Now on to the TableName function.

The reason why I added it is because the article table name doesn’t match the struct’s name. Instead, it’s called articles. If these were both the same, then it would be automatically found by Beego’s ORM.

However, I deliberately changed it as I wanted to show what’s required when your struct and table names differ. Now that we’ve been talking about the table schema, I should include it.

CREATE TABLE "articles" (
    "name" varchar(200) NOT NULL,
    "client" varchar(100),
    "url" varchar(400) DEFAULT NULL,
    "notes" text,
    UNIQUE (name)

Integrating the Models in the Application

Now that we’ve set up and configured our model along with the accompanying form and validation information, we need to make it available in our application. In main.go, we need to add in three more import statements, as below.

_ ""
models "sitepointgoapp/models"

The first one imports Beego’s ORM library, the second one provides support for SQLite3, required because we’re using an SQLite3 database. The third one imports the models we just created, giving them an alias of models.

func init() {
    orm.RegisterDriver("sqlite", orm.DR_Sqlite)
    orm.RegisterDataBase("default", "sqlite3", "database/orm_test.db")

The one final step we need to take is to register the driver, database and models which we’ll use in our application. We do so with the three statements above. We indicate we’re using the SQLite driver, and set it up as the default database connection, connecting to our test database, located in database/orm_test.db.

Finally, we register the models which we’re going to use, in our case, just models.Article.

CRUD Operations

With this done, we now have database support integrated into our application. Let’s get started with the two simpler CRUD operations, delete and update. Neither of these uses a form, as I wanted to keep this section simple, focusing more on the ORM code, as opposed to the form and validation code. We’ll work through this in the Add action.

Deleting A Record

We’re going to set up a delete action which attempts to delete an article from our database, based on the value of the parameter id. In routers/routers.go, add the following route in the init function:

beego.Router("/manage/delete/:id([0-9]+)", &controllers.ManageController{}, "*:Delete")

Then add the code below to controllers/manage.go. Let’s step through it slowly.

func (manage *ManageController) Delete() {
    // convert the string value to an int
    articleId, _ := strconv.Atoi(manage.Ctx.Input.Param(":id"))

Here, we attempt to retrieve the id parameter and convert it to an int from a string, using the Atoi method from the strconv package. This is a simple example, so I’m skipping over any errors which may be thrown and storing the parsed value in articleId.

o := orm.NewOrm()
  article := models.Article{}

Next, we initialize a new ORM instance and specify that we’re using the default database. We could have set up any number of database connections, such as one for reading and one for writing etc. Finally, we created a new, empty, instance of the Article model.

// Check if the article exists first
    if exist := o.QueryTable(article.TableName()).Filter("Id", articleId).Exist(); exist {
        if num, err := o.Delete(&models.Article{Id: articleId}); err == nil {
	    beego.Info("Record Deleted. ", num)
	} else {
	    beego.Error("Record couldn't be deleted. Reason: ", err)
    } else {
	beego.Info("Record Doesn't exist.")

Now to the heart of the function. First, we query the article table, checking if an article with the Id value matching the id parameter exists or not. If it does, we then call the ORM’s Delete method, passing in a new Article object with just the Id property set.

If no error’s returned, then the article was deleted and beego.Info is called to log that the record was deleted using the Info method. If it wasn’t able to do the delete operation, then we call Error instead, passing in the err object, which will display the reason why the record couldn’t be deleted.

Updating a Record

That was deleting a record, now let’s update one; this time we’ll use the flash messenger for a bit more effect.

func (manage *ManageController) Update() {
    o := orm.NewOrm()
    flash := beego.NewFlash()

As before, we initialize an ORM variable and specify the default database. Then we get a handle on the Beego Flash object, which can store messages across requests.

// convert the string value to an int
    if articleId, err := strconv.Atoi(manage.Ctx.Input.Param(":id")); err == nil {
	article := models.Article{Id: articleId}

This time we attempt to retrieve the id parameter and initialize a new Article model if it’s available.

if o.Read(&article) == nil {
	    article.Client = "Sitepoint"
	    article.Url = ""
	    if num, err := o.Update(&article); err == nil 
		flash.Notice("Record Was Updated.")
		beego.Info("Record Was Updated. ", num)

Next, we call the Read method, passing in the Article object, which attempts to load the remaining article properties from the database, if there was a record which matched the id specified in Article.Id.

Assuming that it was available, we set the Client and Url properties on the object and pass it to the Update method, which will update the record in the database.

Assuming that no error occurred, we then call the Notice function on the Flash object passing in a simple message and then call Store to persist the information.

} else {
		flash.Notice("Record Was NOT Updated.")
		beego.Error("Couldn't find article matching id: ", articleId)
	} else {
		flash.Notice("Record Was NOT Updated.")
		beego.Error("Couldn't convert id from a string to a number. ", err)

If something had gone wrong, such as not being able to update the record or we couldn’t convert the id parameter to an integer, we note that in a flash message and in a log message as well.

// redirect afterwards
	manage.Redirect("/manage/view", 302)

Finally, we call the Redirect method, passing in the url we’re going to redirect to and an HTTP status code. What happens now, is that irrespective of whether we could update the record or not, we’ll be redirected to /manage/view, which we’ll se the definition of in a moment.

Viewing All Records

The intent of the View function is two-fold; firstly it displays all existing articles in the article table and displays any flash messages which were set in update. This way, we know if that action succeeded or failed.

func (manage *ManageController) View() {
    flash := beego.ReadFromRequest(&manage.Controller)

    if ok := flash.Data["error"]; ok != "" {
	// Display error messages
	manage.Data["errors"] = ok

    if ok := flash.Data["notice"]; ok != "" {
	// Display error messages
	manage.Data["notices"] = ok

Firstly, we initialize a variable, flash, by reading the request and looking for two properties: error and notice. These correlate to the calls to flash.Notice and flash.Error. If the information is set, we set that in the Data property, so we can access it in the template.

o := orm.NewOrm()

    var articles []*models.Article
    num, err := o.QueryTable("articles").All(&articles)

    if err != orm.ErrNoRows && num > 0 {
	manage.Data["records"] = articles

As with the last two examples, we then set up a connection to the default database and initialize a slice of Article models in articles. We then call the ORM’s QueryTable method, specifying the table name and then call All on that, passing in the articles slice, which will be loaded with the results, should they be available.

Assuming that nothing went wrong, we have records available and we store them in the template variable, records.

Inserting a Record

Now let’s look at inserting a record in the Add action, which covers forms and validation in addition to ORM interaction.

func (manage *ManageController) Add() {
    o := orm.NewOrm()
    article := models.Article{}

I’ll skip this as we’ve already covered it previously.

if err := manage.ParseForm(&article); err != nil {
	beego.Error("Couldn't parse the form. Reason: ", err)
    } else {
	manage.Data["Articles"] = article

Here, we call the ParseForm method, passing in the article object. Assuming that an error wasn’t thrown, we then set article as a template variable, which will help us render a form, as we’ll see shortly.

if manage.Ctx.Input.Method() == "POST" {
	valid := validation.Validation{}
	isValid, _ := valid.Valid(article)
	if !isValid {
	    manage.Data["Errors"] = valid.ErrorsMap
	    beego.Error("Form didn't validate.")
	} else {

Here, we check if the method used was POST. If so we then instantiate a new Validation object and pass the article object to the Valid method, to check if the POST data is valid, according to the rules on the model.

If the data supplied isn’t valid we store any validation errors, available in valid.ErrorsMap, in the template variable Errors as well as log that validation failed. Otherwise, we attempt to insert the article. If there was or wasn’t an error, then we log that.

id, err := o.Insert(&article)
	if err == nil {
	    msg := fmt.Sprintf("Article inserted with id:", id)
	} else {
	    msg := fmt.Sprintf("Couldn't insert new article. Reason: ", err)

Wrapping Up

We’re at the end of the Beego walkthrough. As there are so many features available in the framework, there just isn’t enough space to fit everything into a 2-part series.

What’s more, some of the examples in today’s tutorial may seem a bit odd. The reason for doing that was not for good coding practice, but to highlight the functionality in a semi-real world manner. If you’re thinking that the composition is a little strange, that’s why. That being said, I hope that you’ve enjoyed this short introduction to Beego and that you give the library a try. Considering the time I’ve spent with it so far, I’m really enjoying it and plan to continue using it.

If you have any questions, be sure to check out the online documentation or add a comment on the post. Don’t forget, the code’s available in the Github repository as well, so check it out, fiddle around and experiment.

Tags: beego, framework, go, mvc, web application
Matthew is a freelance technical writer helping businesses win over developers by creating documentation developers need, to really use their platforms to the full. He's also the editor of Master Zend Framework, where you can learn everything there is to know about Zend Framework.

  • hitasoft

    I highly recommend you never leave the normal work, however i also feel this is wonderful!!

  • Richard Eng

    I find your tutorial a bit confusing. You provide a table schema (“CREATE TABLE …”), but do I need to create the table explicitly, or does Beego do it for me?

    Also, in “2-Step Views”, you show lines containing “manage.Layout”, etc., but it’s not clear how to use this in the context of your tutorial. I have to look at your full source code to try and understand what you are trying to do, but a proper tutorial should provide more “hand-holding.”

    If I’m expected to figure out everything by reading your source code, then your tutorial isn’t really much of a tutorial.

    (I come from a Python/web2py background, and I can tell you they have some really solid tutorials that guide you in finer detail, as they should!)

    • William Krause

      You probably already figured this out, but for anyone who reads these comments in the future, the way the tutorial is set-up you need to explicitly create your table in sqlite. This is not strictly necessary in beego, as you can create it from your model structure if you include the correct orm syntax. From what I can tell from looking the source code for the beego project “wetalk”, it seems like a best practice to decouple your form specification from your orm model specification. The author elected to not use the orm for table generation and therefore condensed the form logic into the models.go file (instead of splitting the specification across a forms.go and a models.go file).

      Anyone looking to autogenerate their tables should first read the orm syntax documentation:

      Then the command line documentation:

      What is not clear from the documentation is that you do not need to run syncdb from the command line if you include the following in your code:

      err = orm.RunSyncdb(“default”, false, false)
      if err != nil {

      If you plan on using beego’s automatic api generation, then it may make sense to handcraft your schema (the tool will automatically create the orm syntax based on the table schema it detects). If you plan on using beego as a true mvc framework, it would probably make more sense to learn the orm model syntax to make future schema changes easier.

  • Richard Eng

    This is a very poor tutorial. I downloaded the source code, but it won’t compile–I get tons of errors from SQLite3. I installed SQLite3 (, so why is this happening??? Obviously, the information here is incomplete.

  • William Krause

    Hey Matt, thanks for this tutorial, it really helped clarify a few concepts. Two things though, the schema you provided for the articles table throws up errors due to the inclusion of “AUTOINCREMENT” . This may be a version issue, but from what I can tell sqlite will handle that for us if it’s an int and specified as the primary key, so dropping it from the create table schema was the way I got this tutorial to work for me.

    The other thing I wanted to ask is whether or not you’ve used the “Session” module in beego? I’d love to use beego for more projects (especially with it’s new auto-generated API feature which is perfect for rapid development of SPA’s), but I can’t seem to figure out how to create a proper admin console. I’m currently trying to work it out from the wetalk example available at, but I’m having trouble following how the auth code is working within this context. It would be a huge help to see how to build a simple login form and implement some basic authorization in beego.

Special Offer
Free course!

Git into it! Bonus course Introduction to Git is yours when you take up a free 14 day SitePoint Premium trial.