Converting Ruby sinatra project to Go
Monday 17 October 2022

go

I have a small side project written in Ruby. It uses PostgreSQL database, Sinatra framework and ActiveRecord. The project is meant to replace my goodreads for my use case.

I wanted to practice Go more. So it is a good opportunity to write this same project in Go. My goal is to get to translate my Ruby/Sinatra/ActiveRecord skills to Go tools. And as the project is small it shouldn’t take forever to do it.

The aim for me was to have similar experience as Sinatra classic syntax. and it looks something like this

1get "path/to/handle" do
2// your request handler here
3// return the response as string
4end

in this case get is the request method to handle. it takes two arguments:

  • the path to handle
  • a block to execute (ruby block is just a fancy way to send a closure to a function)

My whole effort was aiming at having this experience when writing the main function. And I broke all the rules to achieve it.

Global Variables

So a basic example for a Go HTTP handler takes the following form

1func(w http.ResponseWriter, req *http.Request) {
2// handle request here and write to response
3}

And then you need to have a muxer that takes this function and execute it based on the path. I used Gorilla mux because it supports dynamic segments in the path.

So if you’re defining a method like the above you have to add it to the mux. but in sinatra you just call get so in order to achieve that. I created a file common.go which will have all common code that is not really my logic. like connecting to DB, running the server. creating the mux…etc stuff that can be ported to any project and I then need to write only the sinatra like functions/blocks

so in common.go I defined an init function which is everything that will be executed before the main function. and another function that starts the server called start so in my main I should define the handlers then call start().

In the init function I defined the mux/router

1router = mux.NewRouter()

Where router is global variable. to add a handler to router the syntax looks like this

1router.Methods("GET").Path(path).HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2// handle request here and write to response
3})

I don’t want to have this in my main code. so better hide it. If I defined a function called GET to hide this ugliness it should look like this

1func GET(path string, handler http.HandlerFunc) {
2	router.Methods("GET").Path(path).HandlerFunc(handler)
3}

so now I can define get handlers in my main like so

1GET("/path/to/handle", func(w http.ResponseWriter, r *http.Request) {
2// handle request here and write to response
3})

That’s better, and same pattern I defined POST and DELETE functions. there is one thing that annoys me. http.ResponseWriter and *http.Request are too verbose. I wish there is a way to make it shorter. So I broke another rule

Type aliases

I defined two type aliases in common.go for http.ResponseWriter and *http.Request

1type Response = http.ResponseWriter
2type Request = *http.Request

Now my handlers will look better

1GET("/path/to/handle", func(w Response, r Request) {
2// handle request here and write to response
3})

All the returns drives me mad

I found that I have a pattern.

  • I get the arguments
  • I query the DB
  • If something is wrong I use http.Error and then return
  • When I want to render something in the middle of the function I render then return.

It looked something like this

 1GET("/path/to/handle", func(w Response, r Request) {
 2  actor := current_user(r)
 3  vars := mux.Vars(r)
 4
 5  user, err := queryForUser(vars["user"])
 6  if err != nil {
 7      http.Error(w, err.String(), http.StatusInternalServerError)
 8      return
 9  }
10
11  if checkPermission() {
12      http.Error(w, err.String(), http.StatusUnauthorized)
13      return
14  }
15})

Wouldn’t it be better to just return the error directly? like so.

 1GET("/path/to/handle", func(w Response, r Request) ???? {
 2  actor := current_user(r)
 3  vars := mux.Vars(r)
 4
 5  user, err := queryForUser(vars["user"])
 6  if err != nil {
 7    return InternalServerError(err)
 8  }
 9
10  if checkPermission() {
11    return Unauthorized(err)
12  }
13
14  return render("something here")
15})

But the http.HandlerFunc signature doesn’t have a return type. so I need to define my own HandlerFunc that’s slightly different.

What the function returns should be able to take the request and response writer and do some logic. soooo it should return a function with this signature

1func(http.ResponseWriter, r *http.Request)

Which is exactly the http.HandlerFunc signature. so our handler should look like so

 1GET("/path/to/handle", func(w Response, r Request) http.HandlerFunc {
 2  actor := current_user(r)
 3  vars := mux.Vars(r)
 4
 5  user, err := queryForUser(vars["user"])
 6  if err != nil {
 7      return InternalServerError(err)
 8  }
 9
10  if checkPermission() {
11      return Unauthorized(err)
12  }
13
14  return render("something here")
15})

To make it more readable I aliased this type to Output so the handler will look like so

 1type Output = http.HandlerFunc // in common.go
 2
 3// in main func
 4GET("/path/to/handle", func(w Response, r Request) Output {
 5  actor := current_user(r)
 6  vars := mux.Vars(r)
 7
 8  user, err := queryForUser(vars["user"])
 9  if err != nil {
10      return InternalServerError(err)
11  }
12
13  if checkPermission() {
14      return Unauthorized(err)
15  }
16
17  return render("something here")
18})

And what InternalServerError returns is Output type. So now I need a function that takes this handler and then calls the Output with w and req

1func InternalServerError(err error) http.HandlerFunc {
2	return func(w http.ResponseWriter, r *http.Request) {
3		http.Error(w, err.Error(), http.StatusInternalServerError)
4	}
5}
6func Unauthorized(w http.ResponseWriter, r *http.Request) {
7	http.Error(w, "", http.StatusUnauthorized)
8}

And using the same pattern you can define all HTTP statuses as functions. And it also works if you want to redirect the user

1func Redirect(url string) http.HandlerFunc {
2	return func(w http.ResponseWriter, r *http.Request) {
3		http.Redirect(w, r, url, http.StatusFound)
4	}
5}

Your handler can use it like so

 1GET("/path/to/handle", func(w Response, r Request) Output {
 2  actor := current_user(r)
 3  vars := mux.Vars(r)
 4
 5  if checkPermission() {
 6      return Redirect("/unauthorized.html")
 7  }
 8
 9  return render("something here")
10
11})

Render views

  • I save all views under views directory as .html files
  • Embed them in a FS variable
  • in the init function it walks the tree of files and parses them using html/template package
  • every file is defined with template name equals it’s relative path to views so views/layout.html is a template called layout
  • created a function partial that renders a template to string
  • then a function Render that returns a http.HandlerFunc that renders the view and the layout with the parameters passed to it
 1//go:embed views
 2var views embed.FS
 3var templates *template.Template
 4var helpers = template.FuncMap{}
 5
 6func compileViews() {
 7	templates = template.New("")
 8	fs.WalkDir(views, ".", func(path string, d fs.DirEntry, err error) error {
 9		if err != nil {
10			return err
11		}
12
13		if strings.HasSuffix(path, VIEWS_EXTENSION) && d.Type().IsRegular() {
14			name := strings.TrimPrefix(path, "views/")
15			name = strings.TrimSuffix(name, VIEWS_EXTENSION)
16			log.Printf("Parsing view: %s", name)
17
18			c, err := fs.ReadFile(views, path)
19			if err != nil {
20				return err
21			}
22
23			template.Must(templates.New(name).Funcs(helpers).Parse(string(c)))
24		}
25
26		return nil
27	})
28}
29
30func partial(path string, data interface{}) string {
31	v := templates.Lookup(path)
32	if v == nil {
33		return "view %s not found"
34	}
35
36	w := bytes.NewBufferString("")
37	err := v.Execute(w, data)
38	if err != nil {
39		return "rendering error " + path + " " + err.Error()
40	}
41
42	return w.String()
43}
44
45func Render(path string, view string, data map[string]interface{}) http.HandlerFunc {
46	return func(w http.ResponseWriter, r *http.Request) {
47		data["view"] = view
48		fmt.Fprint(w, partial(path, data))
49	}
50}

So now I can have a handler that will render a privacy view with layout layout around it using the following code

1GET("/privacy", func(w Response, r Request) Output {
2    return Render("layout", "privacy", map[string]interface{}{
3        "current_user": current_user(r),
4        "csrf":         csrf.TemplateField(r),
5    })
6})

I didn’t like the map[string]interface{} part sooooooo. I defined it as a type in common.go

1type Locals map[string]interface{}

So now it’s cleaner

1GET("/privacy", func(w Response, r Request) Output {
2    return Render("layout", "privacy", Locals{
3        "current_user": current_user(r),
4        "csrf":         csrf.TemplateField(r),
5    })
6})

Helpers

defining views helpers for html/template has a problem to it. You can’t parse templates THEN define functions. the functions map is part of the parsing method. because it validates that views are using existing functions and panics in case a function is used incorrectly in a view/template.

So I have to :

  • define global variable helpers = var helpers = template.FuncMap{} in common.go
  • in my main I can add any function I want to helpers map
  • then call Start() which will compile and templates with helpers map.

Other conveniences

I need to have middlewares that can be executed before handlers to check for session validation for example. so I made the GET, POST, DELETE variant methods. taking a list of middlewares as last arguments so when I’m defining a handler I can pass couple functions like loggedinOnly which checks for current user session.

I also wanted to have CSRF protection. so I used Gorilla CSRF and added it as a default middleware to the server.

Also I needed database connection so I used SQLC to generate code from SQL queries and instantiated the database connection in common.go from environment variable. assigned the Queries instance to queries global variable. so I can access it from any part of the main function.

While I’m at it I also added a logger to the database connection and the server to check what’s going on there.

Also I needed to server static files form public so I added a handler to the server that server files from this directory.

Show me the full code

On github you’ll find all the common code here

I compared the main file from sinatra to the main file after conversion to go and I found it’s around 3 times longer. one main pattern that caused this length is how I can check and return at the same time in go

1return something if userunauhorized(current_user)

In Go

1if err!=nil {
2  return Unauthorized
3}

Also I didn’t use activerecord so alot of the work activerecord models where doing is now part of the database ON DELETE trigger or part of the handler itself.

The following is the original sinatra code And this is when it was converted to Go

See Also