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:
My whole effort was aiming at having this experience when writing the main function. And I broke all the rules to achieve it.
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
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})
I found that I have a pattern.
http.Error
and 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})
views
directory as .html
filesinit
function it walks the tree of files and parses them using html/template
packageviews
so views/layout.html
is a template called layout
partial
that renders a template to stringRender
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})
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 :
helpers = var helpers = template.FuncMap{}
in common.go
helpers
mapStart()
which will compile and templates with helpers
map.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.
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