Go: Building Web Applications With Beego – Part 2

Share this article

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.

Models

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" (
    "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
    "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.

"github.com/astaxie/beego/orm"
_ "github.com/mattn/go-sqlite3"
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")
    orm.RegisterModel(new(models.Article))
}

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()
  o.Using("default")
  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()
    o.Using("default")
    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 = "http://www.google.com"
	    if num, err := o.Update(&article); err == nil 
            {
		flash.Notice("Record Was Updated.")
		flash.Store(&manage.Controller)
		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.")
		flash.Store(&manage.Controller)
		beego.Error("Couldn't find article matching id: ", articleId)
	    }
	} else {
		flash.Notice("Record Was NOT Updated.")
		flash.Store(&manage.Controller)
		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()
    o.Using("default")

    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()
    o.Using("default")
    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)
	    beego.Debug(msg)
	} else {
	    msg := fmt.Sprintf("Couldn't insert new article. Reason: ", err)
	beego.Debug(msg)
			}
		}
	}
}

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.

Frequently Asked Questions (FAQs) about Building Web Applications with Beego

How do I install Beego for building web applications?

To install Beego, you need to have Go installed on your system. Once Go is installed, you can install Beego using the “go get” command. Open your terminal and type “go get github.com/beego/beego/v2@latest”. This command fetches the latest version of Beego and installs it on your system. After the installation, you can verify it by typing “bee version” in the terminal. If Beego is installed correctly, it will display the version of Beego installed on your system.

How do I create a new Beego application?

To create a new Beego application, use the “bee new” command followed by the name of your application. For example, “bee new myapp” will create a new Beego application named “myapp”. This command creates a new directory with the same name as your application and initializes it with a basic Beego application structure.

How do I run a Beego application?

To run a Beego application, navigate to the application directory using the terminal and type “bee run”. This command starts the Beego application and by default, it will be accessible at “http://localhost:8080“. You can change the port by modifying the app.conf file in the conf directory of your application.

How do I use Beego’s ORM for database operations?

Beego’s ORM (Object-Relational Mapping) provides a simple and efficient way to interact with your database. To use it, you need to import the “github.com/beego/beego/v2/client/orm” package in your application. Then, you can register your database using the “orm.RegisterDataBase” function and define your models by structuring Go structs and registering them with “orm.RegisterModel”.

How do I handle HTTP requests in Beego?

Beego provides a simple way to handle HTTP requests. You can define your routes in the router package and associate them with controller functions. These functions will be executed when a request is made to the corresponding route. You can handle different types of requests like GET, POST, PUT, DELETE, etc., by defining functions like Get(), Post(), Put(), Delete(), etc., in your controller.

How do I render views in Beego?

Beego uses the Go template package for rendering views. You can define your views in the views directory of your application. To render a view, use the “this.TplName” variable in your controller function and assign it the name of your view file. For example, “this.TplName = “index.tpl”” will render the “index.tpl” view.

How do I use Beego’s validation package?

Beego’s validation package provides a way to validate your forms and data. To use it, import the “github.com/beego/beego/v2/core/validation” package in your application. You can then define your validation rules using the “validation.Valid” function.

How do I handle errors in Beego?

Beego provides a simple way to handle errors. You can use the “this.Ctx.Output.SetStatus” function to set the HTTP status code and the “this.TplName” variable to render an error view. You can also use the “beego.Error” function to log errors.

How do I use Beego’s logging package?

Beego’s logging package provides a way to log your application’s activities. To use it, import the “github.com/beego/beego/v2/core/logs” package in your application. You can then use the “logs.Info”, “logs.Warning”, “logs.Error”, etc., functions to log information, warnings, errors, etc.

How do I deploy a Beego application?

To deploy a Beego application, you can build it using the “bee pack” command. This command creates a compressed file containing your application and its dependencies. You can then upload this file to your server and extract it. After that, you can run your application by executing the binary file.

Matthew SetterMatthew Setter
View Author

Matthew Setter is a software developer, specialising in reliable, tested, and secure PHP code. He’s also the author of Mezzio Essentials (https://mezzioessentials.com) a comprehensive introduction to developing applications with PHP's Mezzio Framework.

beegoframeworkgomvcweb application
Share this article
Read Next
Get the freshest news and resources for developers, designers and digital creators in your inbox each week