introduction

This side project is a web application of todo list. It has the following components

A working example is hosted on heroku and below is a screenshot. The source code is here on Github.

The basic data types are Ticket and Todo, and each Ticket may contain multiple Todos. The frontend only displays the data retrieved from the URL /data. To modify the database, one types commands in Slack chatroom, for example, to add ticket and todos

/ticket/add id:errand, detail:get groceries
/todo/add ticket_id:errand, item:apples
/todo/add ticket_id:errand, item:oranges

To end ticket and todo,

/ticket/end id:errand
/todo/end ticket_id:errand, idx:1

In this post, I will review a few tricks I learned from this project

HTTP error handling and authentication

In go, a type implements an interface by implementing its methods. This feature can be used to handle HTTP error uniformly, instead of writing error handling code for each HTTP handler function. For this purpose, note that any object satisfies the http.Handler interface by implementing the function

func ServeHTTP(w ResponseWriter, r *Request)

In my code, each HTTP handler function returns its error, as follows

func (app *application) AddTicket(w http.ResponseWriter, req *http.Request) error 
func (app *application) UpdateTicket(w http.ResponseWriter, req *http.Request) error 
func (app *application) EndTicket(w http.ResponseWriter, req *http.Request) error 
func (app *application) DeleteTicket(w http.ResponseWriter, req *http.Request) error 

Here I also make them methods of the application so that they have access to the database. The existence of return value disqualify them as http.Handler interface. And a decorator is used to make them http.Handler as well as handle the error.

// logHandler decorates the http handlers with logging
type logHandler func(http.ResponseWriter, *http.Request) error

func (fn logHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
	if err := fn(w, req); err != nil {
		log.Println(err)
		http.Error(w, err.Error(), http.StatusInternalServerError)
	}
}

Some of my HTTP handlers also require authentication. My strategy is again to decorate the function.

// auth decorates the logHandlers with authentication
func auth(fn logHandler) logHandler {
	authf := func(w http.ResponseWriter, req *http.Request) error {
		if req.Header.Get("Token") != os.Getenv("Token") {
			return fmt.Errorf("Authentication failed.")
		}
		return fn(w, req)
	}
	return logHandler(authf)
}

To register the handlers, I use gorilla/mux

router := mux.NewRouter().StrictSlash(true)
router.Handle("/data", logHandler(app.Data)).Methods("GET")
router.Handle("/slack", logHandler(app.Slack)).Methods("POST")
router.Handle("/todo/delete", auth(logHandler(app.DeleteTodo))).Methods("DELETE")

Note here logHandler(app.Data) is type conversion instead of function call whereas auth(...) is a function call.

heroku deployment

Heroku allows deployment of up to 5 free web applications and I find the service much nicer than pythonanywhere. Automatic deployment can be set up such that all you need to do is to push the new code to github (and hope tests pass).

Heroku does not support sqlite3 but provides free postgres service. The registration and deployment setup are quite straightforward. You can read the details from their documents.

One tricky thing I found is about supporting multiple main.go. In my cmd folder there are two subfolders with main.go: one for the server and the other for database initialization. What worked for me is to run the following command

godep save ./cmd/...

different front-end behaviors of different browsers on different platforms

For my (super light-weight) web application, I hit into two browser/platform-related issues.

One is that flex does not work for safari whereas chrome and firefox work fine. It seems the issue is due to safari and you can read more by googling “flex not working in safari”. The fix is as follows.

.flexchild {
	@apply(--layout-flex);
	-webkit-flex: 1 0 200px; /* hack for safari */
}

Here the @apply(--layout-flex); is specific to Polymer iron-flex-layout.

The other issue is that html symbols are displayed without color in safari on iphone and ipad. And the fix is to add a special character after the symbol, i.e., change \u2714 to \u2714\uFE0E.