DEV Community

Austin
Austin

Posted on

Gonk - The First Steps and Routing

What is Gonk?

Gonk is a project that is part of a four part project that I have planned. Gonk itself is an HTTP Framework I am developing based on the stdlib net/http package from Go. It will include routing, middleware, JSON utility, and more.

Another web framework, really?

I know I'll be asked why I even decided to create a web framework for Go. There are already very well fleshed out frameworks such as Gin or Chi. Let me start off by saying that I am not trying to create the next Express. I'm not trying to compete with any existing HTTP frameworks. Those frameworks are well done, and developed collaboratively by developers who are far better and more experienced than myself.

If you're not trying to compete, why even bother?

The purpose of this project is for learning purposes. When I first started to really take Software Development seriously at the end of 2019, I learned the get hired quick starter pack (AKA: MERN). I'm not discrediting anyone who is a Javascript developer at all. It's a massively used language for a reason. For me though, I've gotten to the point where I want to dive deeper and I want to understand more.

I like many other went to a coding bootcamp, and I had an instructor named Brandon. He was an exceptional teacher, and he not only taught course material, but he introduced ideas that would help you further your journey if you were inquisitive enough to dive into it on your own time. This is where I was first introduced to Go.

For years now, I have wanted to try using Go, and I've even done small projects to get some form of familiarity with Go. I also developed a simple system monitor for Linux last year in Go which can be found on my GitHub (http://github.com/AJMerr/SyMon). I had a lot of fun with this project, but up until this year I really had the wrong mindset. While I really did strive to get better, and really did want to be a great Software Engineer, I was too focused on trying to get a job, and not doing what I really wanted to do and certainly not learning the correct way. I absolutely can do full stack development, but I can't always fully explain how my system architecture works past a high level understanding. This is fine for a Junior Software Engineer (from what I've seen everyone say at least.) as I can get shippable code produced, and I can tell you what it does. I also have become pretty decent at understanding how to debug my code.

I say all this to say that, while I would love to get a job as a Software Engineer right away, I have done enough deployed, fully demo ready web apps. I have two pretty cool projects that mirror each other, but one is self hostable and the other is meant to be used on the cloud. It's a photo sharing app called Little Moments (http://www.github.com/AJMerr/little-moments and http://www.github.com/AJMerr/little-moments-offline). These coupled with some of my other projects are resume ready and show enough of my current skillset. I need to focus on TRULY understand how everything I do works, and how to explain this to a lower level. I let myself get wrapped up in the "Do these projects NOW to get hired" hell rather than continuing to develop myself as an engineer further. Hindsight is 20/20, but if I could go back 6 years ago, I would focus more on learning than getting hired.

Why Go?

With all the current resources at my disposal, there is no better time to dive deep into developing myself into being a better Engineer. With my interest piqued in Go, I decided to do Little Moments - Offline's back end completely in Go. This was a great project to get me familiar with how things work and to decide if I liked the language, or if I would go a different route. I knew, however, that there was something about Javascript that really just didn't give me that itch. I loved the end result, but I couldn't really say that I enjoyed the process with Javascript. This isn't to say it is a bad language at all, but it's just a feeling you get that I can't explain.

When writing Go, even if I had to copy down code with certain aspects of Little Moments - Offline, I was really engaged and always looked into why I did something a certain way or what the importance of it was. I do use LLMs, but I use them as a learning tool, not as a crutch. Being able to ask ChatGPT to explain why a certain method works and how it works as well as citing resources and documentation makes the learning process so much easier.

There are plenty of technical reasons to use Go, but my focus on this post is to be honest about it not really coming down to performance, lower level programming, or anything that I could say to explain why Go is a great language to learn, but honestly that I just really enjoy working with Go. If you're focused on becoming a better developer, and you don't have a job in Software Engineer, then I think that focusing on what you enjoy doing is an important trait that is missed.

I don't do this because I want the money or status. I do this because I love it.

TL;DR

I decided to do this project because it's an opportunity to learn how the net/http package works at a lower level. I can focus on why certain methods work the way they do, and I can grow my understanding and skillset more.

I chose Go because I like it and it keeps me wanting to code every day.

Initial Setup

The first thing I did for this project was define it's structure and create a repo on GitHub for it.

  • Repo initialization
  • Go Module
    • Ran go mod init github.com/AJMerr/gonk

I also created a Makefile, but I'm not entirely sold that I did it well, and it's something I will need to refine later.

Building the Router

The very first feature I wanted to implement was routing. Honestly, with http.ServeMux it's not terrible from the sdblib net/http but simplifying this makes a good ergonomic experience.

I'm not re-writing http.ServeMuc, I'm just making a wrapper around ti to make the experience using it more ergonomic. My wrapper does the following:

  1. Implements http.Hanlder so that it can be plugged into http.ListenAndServer
  2. Provides helper methods like GET or DELETE for example. This just helps with writing less boiler plate.

Defining the Router

So the first thing I did was create a struct that defined what a router is.

type Router struct {
    mux *http.ServeMux
}
Enter fullscreen mode Exit fullscreen mode

Router is now a pointer to http.ServeMux in order to make the data from http.ServeMux mutable.

The next thing I did was create a NewRouter() constructor that initializes this with http.NewServeMux

This ensures the internal routing table is set up properly (zero-value ServeMux has a nil map — if you try to use it, you’ll panic).

Implement Handle

This is a wrapper that registers routes

func (r *Router) Handle(p string, h http.Handler) {
    if r.mux == nil {
        panic("Router is not initialized, use NewRouter()")
    }
    r.mux.Handle(p, h)
}
Enter fullscreen mode Exit fullscreen mode

In Go 1.22, ServeMux already support patterns such as "GET /notes or DELETE /notes/{id}. This made the Handle method pretty easy to setup

Implement ServeHTTP

This makes my Router a valid http.Handler:

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request) {
    if r.mux == nil {
        panic("Router is not initialized, use NewRouter()")
    }
    r.mux.ServeHTTP(w, req)
}
Enter fullscreen mode Exit fullscreen mode

Now I can pass r directly to http.ListenAndServe.

Method Specific Helpers

So when I initially used this, I was happy that I really understood what was going on, and I could visualize in my head everything needed and why it was needed. I know I said that I use LLMs at times, but none of this code was Code Gen'd. I SPECIFICALLY prompted in that I would not be provided and code, and only to be pointed to documentation when I needed help understanding what was going wrong.

Anyways, back to the code. All of the above resulted in this:

r.Handle("GET /healthz", http.HandlerFunc(healthzHandler))

This was cool to see working, but this wasn't any less verbose than using http.ServeMux. My solution? Here is how I handle METHOD specific helpers with GET being the example:

func (r *Router) GET(p string, fn func(w http.ResponseWriter, r *http.Request)) {

if r.mux == nil {

panic("Router is not initialized, use NewRouter()")

}

handler := http.HandlerFunc(fn)

// Adds GET to path

fullP := "GET " + p

r.Handle(fullP, handler)

}
Enter fullscreen mode Exit fullscreen mode

The formatting is better inside my editor, I promise. Anyway, the end result is:

r.GET("/healhtz", healthzHandler)

That is a lot better.

Final Testing

So, just to ensure this works, I tested it with an example healthz route

func healthzHandler(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Content-Type:", "application/json, charset-utf8")

    status := struct {
        OK bool `json:"ok"`
    }{
        OK: true,

    }

    err := json.NewEncoder(w).Encode(status)
        if err != nil {
        http.Error(w, "{ok: false}", http.StatusInternalServerError)
}

func main() {
    r := router.NewRouter()

    r.GET("/healthz", healthzHandler)

    http.ListenAndServe(":8080", r)
}
Enter fullscreen mode Exit fullscreen mode

The end result?

Fully working routing!

I also tested different routes and they all worked. I really enjoyed this experience, and I really am starting to understand what's going on to a lower level. I'm not finished with this project, and I will write more articles as I build this out further.

Follow the repo here: http://www.github.com/AJMerr/gonk

Top comments (0)