Display database table in html table like gridview

https://play.golang.org/p/QqHXewyDTp
Here’s my code on playground.
The /read route ain’t working. Showing an empty html page

The playground does not support external libraries, so it is not possible to execute your code.

What result do you expect? What result do you get? How is it wrong?

Thanks @lutzhorn I didn’t run it on playground just used it to share the codes. I expected to see my db contents displayed on the web like how it does on the console. But it was just a blank web page. Maybe my range didn’t work as expected. Cos the rows.next() results is saved in the struct.

line 117 has a return statement that will be encountered the first time through the loop.

what the hell!!! That was just my error?
It works.
Left this code through the night cos I couldn’t figure that out.
Will continue my go learning.
Thanks @nathankerr

Would you like some more feedback on your code?

yeah. that will be great. Am like 1.2months old in golang. Have a project coming up so had to start learning. Am right now working on update.
So i’ll gladly accept and respect your contribution @nathankerr

I’m only giving feedback on readUser because I already read and understood the function. Most of what I have to say is generally applicable. And, frankly, there is enough to cover here.

Scan into struct

You can scan directly into the members of a struct. Instead of:

var id int
var username string
var password string
var created_at mysql.NullTime
var updated_at mysql.NullTime
var isAdmin int
err = rows.Scan(&id, &username, &password, &created_at, &updated_at, &isAdmin)

You can write:

var pd PersonData
err = rows.Scan(&pd.Id, &pd.Username, &pd.Password, &pd.Created_at, &pd.Updated_at, &pd.AdminD)

SELECT *

Imagine a new column is added to new_table in your database. What would happen to this code?

What should happen? It should keep working as it did before the column was added. PersonData doesn’t have a field to put the new data in. The template doesn’t try to use the new data.

What will happen? rows.Scan will start returning errors complaining that number of values in the row doesn’t match the number of values you are scanning into.

This unfortunate situation can be avoided by explicitly selecting the columns you need from the table (e.g., select id, username from table). An additional benefit from this approach is that since you have to write the column names twice (once in Query and once in Scan), you have a natural incentive to not request data you don’t need, which also means the database doesn’t waste resources producing data that isn’t used. All of that combines to reduce latency and increase performance.

Writing out the column names in select statements makes your program more robust and improves performance.

error handling

My initial thought of why readUser was returning a black page was because the error returned by templ.ExecuteTemplate was not checked. Unchecked errors are always the first thing I look for when something isn’t working because the errors frequently tell me why it isn’t working. When there aren’t any errors, that also tells me something.

errcheck is a tool that will tell you which errors are not checked.

Now to look at the error handling itself, starting with checkErr. From its name, I can’t tell what the error is checked for or what happens when the error matches. My guess (since it was not included with the code you provided), from the context and lack of additional control flow, is that checkErr is:

func checkErr(err error) {
	if err != nil {
		panic(err.Error())
	}
	return
}

I assume it panics on error because of the absence of control flow (e.g., return) when checkErr is called.

The problem here isn’t that you used a function to check an error. A good example of this type of function is os.IsExist, which checks to see if the error is one of the many errors that could happen when a file already exists.

The first problem I have with checkErr is its name. A better name (assuming my guess of what checkErr does is correct) is panicOnErr. This name tells me the control flow (panic) and the condition (existence) when the function panics.

The next problem comes from what happens when an error is encountered and the symptoms that are present to diagnose that error. You told us:

The /read route ain’t working. Showing an empty html page

From this description there is no way to tell if an error was encountered or not. For example, if db.Query returned an error, checkErr would panic, preventing the further execution of readUser. Since http.Server recovers from panics, the program would not die, but the response would be written with whatever state it was in. In this case, a black page with an OK (200) status.

This outcome (a blank page with an OK (200) status) is the same thing that happened when the return statement was encountered.

With the return statement removed, this outcome could also happen if templ.ExecuteTemplate had an error (even if it were handled with checkErr).

I know that when http.Server recovers from a panic it puts the stack trace from that panic to the console. This means that you * can* tell if there was an error or not by looking at output from the server in your terminal.

However, the client (e.g., the web browser) thinks that the blank page is what you intended, because the response status was OK (200).

Now we get to my real problems with checkErr.

Since checkErr doesn’t do what is needed, you need to write some custom error handling code, at least enough so the client knows if the outcome was intended or not.

Should it be a function like httpErr(res http.ResponseWriter, req *http.Request, err Error)? Only if you want to end the HTTP request by panicing. Otherwise you have to write something like:

if err != nil {
	//...
	return
}

anyway. Besides that, the Go Proverb is “Don’t panic”.

So, for anything other than panicing on error you will need an if block.

Since you are used to seeing the single line for checkErr, these error handling blocks will look and feel cumbersome to you.

I find that by always using them (i.e., not using checkErr) the pattern that results lets me easily skip over the error handling when reading the code while at the same time lets me figure out what is done when an error occurs because the code is right there.

Furthermore, since error handling is some of the most likely code to be customized or need to do something that can’t be done in another function (e.g., return) the if block needs to be written anyway. And when it is time to customize it, you can focus on the changes instead of the whole thing.

For another perspective on this, read Code boilerplate: Is it always bad?

A version of readUser

Applying the feedback above (other than expanding select *) results in something like:

func readUser(res http.ResponseWriter, req *http.Request) {
	rows, err := db.Query("SELECT * FROM new_table")
	if err != nil {
		log.Println(err)
		http.Error(res, "there was an error", http.StatusInternalServerError)
		return
	}

	var ps []PersonData
	for rows.Next() {
		var pd PersonData

		err = rows.Scan(&pd.Id, &pd.Username, &pd.Password, &pd.Created_at, &pd.Updated_at, &pd.AdminD)
		if err != nil {
			log.Println(err)
			http.Error(res, "there was an error", http.StatusInternalServerError)
			return
		}

		ps = append(ps, pd)
	}

	err = templ.ExecuteTemplate(res, "read.html", ps)
	if err != nil {
		log.Println(err)
		http.Error(res, "there was an error", http.StatusInternalServerError)
		return
	}
}

Now when an error occurs, it logs it to the console and the client also knows there was an error. Put the return statement back in and see if you can tell there was an early return.

3 Likes

wow!! how do you know all these things? Like you’re google-fu.
so when dealing with templates, panic isn’t a good way to go about it.
I’m so grateful, this is like a version1.2 for me.
But can’t i just put [quote=“nathankerr, post:8, topic:5848”]
if err != nil {
log.Println(err)
http.Error(res, “there was an error”, http.StatusInternalServerError)
return
}
[/quote]
into checkErr()?
And is there something like a flash msg? cos like in my creatUser i used res.Write() which writes in a different page just want it still on that same page.
Apart from the error handling, yours looks shorter and still clean.
Thanks @nathankerr

You could put:

log.Println(err)
http.Error(res, "there was an error", http.StatusInternalServerError)

into checkErr.

However, putting return in checkErr would return from checkErr back to readUser where execution would continue. This would cause other problems like rows not being properly initialized (as indicated by the existence of an error), which would then cause problems throughout the rest of the function.

And is there something like a flash msg?

Use Gorilla, the golang web toolkit

how do you know all these things?

Mostly by writing code, seeing if it does what I think it should do, and learning from when it doesn’t (which is frequently the case).

panic isn’t a good way to go about it

panic is almost never the correct thing to do.

1 Like

Huh! almost started using it in my checkErr(). So I can’t use a function to control errors like try catch?

That means to use flash i have to use session. Never done or created a real life app that is used by people; that involves login/logout, password recovery/remember me, database, upload of photos etc. And my upcoming project has to deal with all these. Am doom. All do I really get all these straight up-like i tried a small photoblog the other day with gorilla sessions and it failed like I spent the whole day trying to get login to work, just left it.

I love programming but it makes me tired.

So I have to now insert my error codes in anywhere i wanna use it. that’s great!
Thanks @nathankerr

Web development is hard because of all the interacting problems.

Web Development with Go will give you a good overview of everything and help you put them together. @joncalhoun, the author, did all the research and shows you how to put it all together. It should cover everything you need to know for your upcoming project.

3 Likes

but you have done web-stuffs like these. So you can be my senior colleague. So you can send me ya link-up; mail/hangout, facebook/messenger, twitter etc where you can help. Nice meeting you, learnt a lot in 6hrs than in a week of class.
I wanted to ask
why is
`
func checkErr(res http.ResponseWriter, err error) {
if err != nil {
log.Println(err)
http.Error(res, “there was an error”, http.StatusInternalServerError)
return
}
}

different from using it that way cos there's still a return statement there

Since I had a lot to say about checkErr, I wrote an article for my site:

Why You Should Not Use checkErr

It is more complete and organized than my response above, so it is still worth reading.

2 Likes

wow! that’s quick

Figured I would get it done while everything was still in my head :slight_smile:

yeah so this question is solve. Now my update is semi-working. Nice blog post though.

1 Like

oh! pocket goher(s) why the ‘s’? Are you a group/team or an individual?

I am an individual and am currently the only one working on https://pocketgophers.com

Part of the reason for the “s” is that some presentations, such as https://blog.golang.org/concurrency-is-not-parallelism, use pictures of gophers to illustrate how Go programs work. Since I am also describing how Go works, the site can be thought of as a bestiary of gophers. Sadly I’m not much of an artist, so there aren’t drawings of the different gophers. I have to get them from https://github.com/ashleymcnamara/gophers and other artists in the community.

2 Likes

that’s really great, i’m inspired. Did a lil blog post of my whole program anyway to assist my team members.
Am new to blogging so it’s just so strange

1 Like