Simple web server with GO!

Introduction

In this blog post, we'll walk through the process of creating a simple web server in Go using the "gorilla/mux" package. We'll cover the basics of setting up a server, handling different routes, serving static files, and even creating a basic JSON API endpoint. By the end of this tutorial, you'll have a better understanding of how to build a web server in Go and handle different types of requests.

Prerequisites

Before we make a progress, make sure you have Go installed on your machine. You can download and install the latest version from the official Go website

Setting up the Project

Create a new directory for your project and navigate to it in your terminal. Then, create a new file called "main.go" and open it in your favourite text editor.

Importing the Required Packages

At the beginning of the "main.go" file, we'll import the necessary packages for our project. Alternatively, You can use the GO extensions for IDEs like Visual Studio Code which provides features like auto importing packages while you coding.


import (
    "encoding/json"
    "fmt"
    "log"
    "net/http"
    "os"
    "time"
    "github.com/gorilla/mux"
)

Here, we're importing the "encoding/json" package for handling JSON data, the "fmt" package for printing output, the "log" package for logging, the "net/http" package for handling HTTP requests and responses, the "os" package for accessing environment variables, the "time" package for handling timeouts, and the "github.com/gorilla/mux" package for routing and URL matching.

Routing with Gorilla Mux

The "gorilla/mux" package is a popular third-party router for Go that provides powerful URL matching and routing capabilities. Instead of using the built-in "http.ServeMux" router, which has limited functionality, "gorilla/mux" offers a more flexible and feature-rich routing system.

In the code, we create a new router instance using "mux.NewRouter()". This router instance acts as the central hub for defining and managing our routes.


r := mux.NewRouter()

We then define our routes using the "r.HandleFunc()" method, which takes three arguments: the route pattern, the handler function, and the HTTP methods allowed for that route.


r.HandleFunc("/", helloHandler).Methods("GET")
r.HandleFunc("/greet", greetHandler).Methods("GET")
r.HandleFunc("/api/json", jsonHandler).Methods("GET")

The first argument ("/", "/greet", "/api/json") represents the URL pattern that the route should match. The second argument is the handler function that will be called when the route is matched. The third argument, "Methods("GET")", specifies the HTTP methods that are allowed for the route (in this case, only GET requests).

Gorilla Mux also supports advanced routing features like URL parameter matching, regular expression matching, and subrouting. This makes it easier to build complex routing structures and handle various URL patterns.

Logging Middleware

Middleware is a concept in web development that allows you to intercept and process requests before they reach the final handler function. Middleware functions can perform tasks like logging, authentication, rate limiting, and more.

In the code, we define a logging middleware function called "loggingMiddleware":


func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    log.Printf("%s %s %s\n", r.Method, r.RequestURI, r.RemoteAddr)
    next.ServeHTTP(w, r)
    })
}

This middleware function takes an "http.Handler" as an argument (the "next" handler in the chain) and returns a new "http.Handler". The returned handler is a closure that logs the request method, request URI, and remote address before calling the "next.ServeHTTP(w, r)" method, which passes the request to the next handler in the chain.

We apply this middleware to the router using "r.Use(loggingMiddleware)". This means that every incoming request will go through the "loggingMiddleware" function before reaching the respective route handler.

Handler Functions

Now, let's define our handler functions for different routes:

1. Root Path Handler


func helloHandler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello, World!")
}

This handler function simply writes "Hello, World!" to the response writer when the root path ("/") is accessed.

2. Greeting Handler with Query Parameters


func greetHandler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if name == "" {
name = "Guest"
}
fmt.Fprintf(w, "Hello, %s!", name)
}

This handler function responds with a greeting message that includes the name provided in the query parameter ("?name=YourName"). If no name is provided, it defaults to "Guest".

3. JSON API Handler


// JSON response struct
type jsonResponse struct {
Message string `json:"message"`
}


// Handler for a JSON API endpoint
func jsonHandler(w http.ResponseWriter, r *http.Request) {
response := jsonResponse{Message: "Hello, JSON World!"}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}

Here, we define a "jsonResponse" struct to represent our JSON response data:


// JSON response struct
type jsonResponse struct {
Message string `json:"message"`
}

The 'json:"message"' struct tag tells the JSON encoder/decoder to use the field name "message" when encoding or decoding the 'Message' field.

In the 'jsonHandler' function, we create an instance of the 'jsonResponse' struct, set the 'Content-Type' header to 'application/json', and encode the struct as JSON using 'json.NewEncoder(w).Encode(response)':


// Handler for a JSON API endpoint
func jsonHandler(w http.ResponseWriter, r *http.Request) {
response := jsonResponse{Message: "Hello, JSON World!"}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}

When a client sends a request to the '/api/json' route, the 'jsonHandler' function will be called, and it will respond with the JSON-encoded `jsonResponse` struct.

4. Static File Handler


func staticFileHandler(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "./static"+r.URL.Path)
}

This handler function serves static files (e.g., HTML, CSS, JavaScript) from the './static' directory. It appends the requested file path to the './static' directory and serves the file using 'http.ServeFile'.

Serving Static Files

Serving static files like HTML, CSS, and JavaScript is a common requirement for web applications. In the code, we use the `http.FileServer` handler to serve static files from the './static' directory:

 
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))

Here's what's happening:

1. 'http.Dir("./static")' creates a new 'http.Dir' value that represents the './static' directory on the filesystem.

2. 'http.FileServer(http.Dir("./static"))' creates a new 'http.Handler' that serves files from the './static' directory.

3. 'http.StripPrefix("/static/", http.FileServer(http.Dir("./static")))' creates a new 'http.Handler' that strips the "/static/" prefix from the request URL before serving the file.

4. 'r.PathPrefix("/static/").Handler(...)' registers the handler created in the previous step to handle all requests with a path prefix of '/static/'.

When a client requests a URL like `http://localhost:8080/static/index.html`, the server will serve the `./static/index.html` file. If the requested file doesn't exist, the server will respond with a 404 Not Found error.

This approach allows you to separate your static assets (HTML, CSS, JavaScript) from your Go code, making it easier to manage and serve those files. By placing your static files in the './static' directory and using the 'http.FileServer' handler, you can serve them without having to embed them in your Go code or use a separate web server for static file hosting. This separation of concerns makes your codebase more organized and maintainable, while also simplifying the process of updating or modifying your static assets without touching the Go code.

Main Function

In the 'main' function, we'll set up the router, define routes, and start the server:


func main() {
// Create a new router
r := mux.NewRouter()


// Add routes and their handlers
r.HandleFunc("/", helloHandler).Methods("GET")
r.HandleFunc("/greet", greetHandler).Methods("GET")
r.HandleFunc("/api/json", jsonHandler).Methods("GET")

// Serve static files from the "static" directory
r.PathPrefix("/static/").Handler(http.StripPrefix("/static/", http.FileServer(http.Dir("./static"))))


// Apply logging middleware
r.Use(loggingMiddleware)


// Start the server
addr := ":8080"
if port := os.Getenv("PORT"); port != "" {
addr = ":" + port
}
srv := &http.Server{
Handler: r,
Addr: addr,
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}


log.Printf("Starting server at %s\n", addr)
if err := srv.ListenAndServe(); err != nil {
log.Fatal(err)
}
}


1. We create a new router instance using 'mux.NewRouter()'.

2. We define our routes and their corresponding handler functions using 'r.HandleFunc()'. We specify the HTTP method ('Methods("GET")') for each route.

3. We set up a handler for serving static files from the './static' directory using 'r.PathPrefix()' and 'http.FileServer'.

4. We apply the logging middleware to log incoming requests using 'r.Use(loggingMiddleware)'.

5. We determine the server address ('addr') based on the 'PORT' environment variable or use the default port ':8080'.

6. We create an 'http.Server' instance with the router as the handler, the server address, and timeouts for writing and reading.

7. We log the server address and start the server using 'srv.ListenAndServe()'.

Running the Server

To run the server, navigate to your project directory in the terminal and execute the following command:


go run main.go

You should see the log message 'Starting server at :8080' (or the port specified by the 'PORT' environment variable).

Testing the Server

With the server running, you can test the different routes and functionalities using a web browser or a tool like 'curl':

1. Visit 'http://localhost:8080/' in your web browser, and you should see the "Hello, World!" message.

2. Visit 'http://localhost:8080/greet?name=Vinodh' to see a greeting with the provided name (replace "Vinodh" with your desired name).

3. Visit 'http://localhost:8080/api/json' to see the JSON response '{"message":"Hello, JSON World!"}'.

4. Place some static files (e.g., HTML, CSS, JavaScript) in the './static' directory and visit 'http://localhost:8080/static/index.html' to serve those files.

Conclusion

In this blog post, we've covered the basics of building a simple web server in Go using the 'gorilla/mux' package. We've learned how to set up routes, handle different types of requests (including JSON API requests), serve static files, and apply middleware for logging. With this knowledge, you can build upon this foundation and create more complex web applications in Go.

I will enhance the server with additional features, such as database integration, authentication, and more advanced routing patterns. Sooooo... Keep in touch!

Code - click here


Post a Comment

Previous Post Next Post