How I Wrote Express-Go in 19 Hours

If you’ve ever worked with web frameworks like Express.js, you know how convenient and easy to use they can be. Now, imagine this ease in Go, with its performance and robustness. Well, that motivated me to create express-go, a micro-framework inspire…


This content originally appeared on DEV Community and was authored by Bruno Ciccarino λ

silicon valley

If you've ever worked with web frameworks like Express.js, you know how convenient and easy to use they can be. Now, imagine this ease in Go, with its performance and robustness. Well, that motivated me to create express-go, a micro-framework inspired by Express.js, and best of all: I built it in 19 hours! The journey was intense, but worth every second. Let me tell you how it all happened. Official repository link

the idea

It all started when I thought: "It would be cool to have something simple like Express.js, but with the performance of Go!". Go is already known for being minimalist and performant, but when it came to writing web servers, I felt something easier to use like Express.js was still missing.

So instead of complaining, I decided to get my hands dirty and make something happen. I was determined to create a micro-framework that would allow me to configure routes, handle HTTP requests and responses quickly and easily.

The Beginning of the Journey

I started with the basic structure: a Go application that could listen to HTTP requests and, depending on the route, perform different functions.

First Stop: Routes

The first thing I needed to do was set up the routing. I wish it was possible to define routes in a similar way to Express.js, where you specify a URL and a function to handle that route.

Here's the magic of routes:

type App struct {
    routes map[string]func(req *req.Request, res *req.Response)
}

func NewApp() *App {
    return &App{
        routes: make(map[string]func(req *req.Request, res *req.Response)),
    }
}

func (a *App) Route(path string, handler func(req *req.Request, res *req.Response)) {
    a.routes[path] = handler
}

func (a *App) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    if handler, exists := a.routes[r.URL.Path]; exists {
        request := req.NewRequest(r)
        response := req.NewResponse(w)
        handler(request, response)
    } else {
        http.NotFound(w, r)
    }
}

func (a *App) Listen(addr string) error {
    return http.ListenAndServe(addr, a)
}

The idea here was simple: I wanted a route map (map[string]func) where the key was the URL and the value was the function that would handle the request.

The Magic of the Handler

One of the things I liked most about Express.js was how easy to use route handlers are. So, I adopted the idea that each route would be just a function that would receive two parameters: the request and the response. In Go, this is a bit more work, as the standard library requires a lot of manual work, so I wrote some abstractions to make it easier.

Handling Requests
HTTP requests in Go involve a lot of structures and methods, so I encapsulated all of this in a struct called Request, with some convenient methods for getting query parameters, headers, and the request body.

type Request struct {
    Req  *http.Request
    Body string
}

func NewRequest(req *http.Request) *Request {

    bodyBytes, _ := io.ReadAll(req.Body)
    bodyString := string(bodyBytes)

    return &Request{
        Req:  req,
        Body: bodyString,
    }
}

func (r *Request) QueryParam(key string) string {
    params := r.Req.URL.Query()
    return params.Get(key)
}

func (r *Request) Header(key string) string {
    return r.Req.Header.Get(key)
}

func (r *Request) BodyAsString() string {
    return r.Body
}

Now, instead of dealing with the http.Request directly, I can do something like:

app.Route("/greet", func(r *req.Request, w *req.Response) {
    name := r.QueryParam("name")
    if name == "" {
        name = "Guest"
    }
    w.Send("Hello, " + name + "!")
})

This makes things much cleaner and more readable!

Responding Easy

After the requests, it was time to make it easier to send responses. Response also needed a touch of simplicity so I could send text or JSONs quickly.

type Response struct {
    http.ResponseWriter
}

func NewResponse(w http.ResponseWriter) *Response {
    return &Response{w}
}

func (res *Response) Send(data string) {
    res.Write([]byte(data))
}

func (res *Response) Status(statusCode int) *Response {
    res.WriteHeader(statusCode)
    return res
}

func (res *Response) JSON(data interface{}) {
    res.Header().Set("Content-Type", "application/json")
    jsonData, err := json.Marshal(data)
    if err != nil {
        res.Status(http.StatusInternalServerError).Send("Error encoding JSON")
        return
    }
    res.Write(jsonData)
}

The Result

At the end of these 19 hours of work, I managed to create express-go: a fast and easy-to-use micro-framework, where configuring routes and sending responses is as simple as Express.js, but with all the power and performance of Go.

Usage Example:

Here's a complete example of how it all fits together:

package main

import (
    "express-go/router"
    "express-go/req"
    "fmt"
)

func main() {
    app := router.NewApp()

    app.Route("/hello", func(r *req.Request, w *req.Response) {
        w.Send("Hello, World!")
    })

    app.Route("/json", func(r *req.Request, w *req.Response) {
        w.JSON(map[string]string{"message": "Hello, JSON"})
    })

    fmt.Println("Server listening on port 3333")
    app.Listen(":3333")
}

Simple, clean and to the point. I'm proud to say that I was able to build this in less than a day, and the cool thing is that it offers enough flexibility for small projects, without all the complexity of larger frameworks.

Final Reflection

Creating the express-go in 19 hours was a fun and challenging journey. I focused on solving real problems I've faced with Go servers and tried to make everything as intuitive as possible. Of course, there's more work to be done, but there's plenty to play with!

If you're curious, take a look at the code and feel free to contribute. After all, building tools like this is much cooler when we can share the process!

Now, if you'll excuse me, I'm going to get a coffee... after 19 hours, I deserve it, right?


This content originally appeared on DEV Community and was authored by Bruno Ciccarino λ


Print Share Comment Cite Upload Translate Updates
APA

Bruno Ciccarino λ | Sciencx (2024-10-19T18:56:55+00:00) How I Wrote Express-Go in 19 Hours. Retrieved from https://www.scien.cx/2024/10/19/how-i-wrote-express-go-in-19-hours/

MLA
" » How I Wrote Express-Go in 19 Hours." Bruno Ciccarino λ | Sciencx - Saturday October 19, 2024, https://www.scien.cx/2024/10/19/how-i-wrote-express-go-in-19-hours/
HARVARD
Bruno Ciccarino λ | Sciencx Saturday October 19, 2024 » How I Wrote Express-Go in 19 Hours., viewed ,<https://www.scien.cx/2024/10/19/how-i-wrote-express-go-in-19-hours/>
VANCOUVER
Bruno Ciccarino λ | Sciencx - » How I Wrote Express-Go in 19 Hours. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2024/10/19/how-i-wrote-express-go-in-19-hours/
CHICAGO
" » How I Wrote Express-Go in 19 Hours." Bruno Ciccarino λ | Sciencx - Accessed . https://www.scien.cx/2024/10/19/how-i-wrote-express-go-in-19-hours/
IEEE
" » How I Wrote Express-Go in 19 Hours." Bruno Ciccarino λ | Sciencx [Online]. Available: https://www.scien.cx/2024/10/19/how-i-wrote-express-go-in-19-hours/. [Accessed: ]
rf:citation
» How I Wrote Express-Go in 19 Hours | Bruno Ciccarino λ | Sciencx | https://www.scien.cx/2024/10/19/how-i-wrote-express-go-in-19-hours/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.