DEV Community

Ansu Jain
Ansu Jain

Posted on

Mastering Middleware in Go: Tips, Tricks, and Real-World Use Cases

As a developer coming from a Java background, one of the first things I thought about when starting to work with Go was whether there was a similar concept to Filters that I had grown accustomed to. Fortunately, Go has its own way of handling middleware, and it's a powerful feature that can help you write better, more maintainable code.

Middleware is a powerful concept in Go that allows you to intercept and modify HTTP requests and responses. It’s a way to add functionality to your HTTP server without modifying your core application logic. Middleware is essentially a chain of functions that can be executed before and after the main request handler. It can perform tasks like authentication, logging, request validation, response compression, and much more.

In this article, we’ll explore the basics of middleware in Go and take a deep dive into some real-world use cases.

Let’s understand first Why should we use Middleware in Go?

We should use middleware in Go because it provides a modular and extensible way to add functionality to our web application. Middleware sits between the incoming HTTP request and the application’s final response, allowing us to add features like authentication, request logging, and error handling. By using middleware, we can keep our application’s core logic clean and focused, and reduce code duplication.

What principle will break if we don’t use middleware in Go?

If we don’t use middleware in Go, we risk violating the single responsibility principle. This principle states that a module, class, or function should only have one reason to change. If we don’t use middleware, we may be tempted to add features like authentication or request logging directly to our application’s request handlers. This can lead to bloated, hard-to-maintain code that is difficult to change.

Which scenarios is middleware very helpful?

Middleware is very helpful in scenarios where we need to perform common tasks on incoming HTTP requests, such as authentication, authorization, request validation, and logging. Middleware allows us to apply these tasks consistently across our application, reducing code duplication and making it easier to maintain and modify our code.

What all middleware must we have in our code?

The middleware that we need in our code depends on the specific requirements of our application. However, there are several middleware functions that are commonly used in web applications, including:

Authentication middleware: This middleware checks whether the user is authenticated and authorized to access the requested resource.
Logging middleware: This middleware logs information about incoming requests, including request method, URL, headers, and response status.
Error handling middleware: This middleware catches errors that occur during request handling and returns an appropriate error response to the client.
Request validation middleware: This middleware validates incoming requests to ensure that they meet certain criteria, such as HTTP method, headers, query parameters, and request body content.
Caching middleware: This middleware caches responses to certain requests to improve performance and reduce server load.
Request Tracing: This middleware is used to trace the path of a request through a web application. It captures relevant information about the request and logs it for monitoring and debugging purposes.
The question that could be coming to your mind next is:

How to incorporate Middleware in Go?

Go has a built-in net/http package that provides a basic HTTP server. To use middleware with this server, we simply need to wrap the original handler with our middleware function.

There are many third-party packages available that provide more advanced functionality. One of the most popular is Gin, a web framework that provides a powerful set of tools for building web applications. We also use in our org gin Webframework.

Gin uses a custom Context object instead of the http.ResponseWriter and *http.Request objects used in the standard net/http package. This Context object provides additional functionality and allows middleware functions to perform more advanced tasks.

To add middleware in Gin, we can use the Use() method of the gin.Engine object. Here's an example of how we can add middleware to a Gin application:

package main

import (
 "fmt"
 "net/http"
 "time"

 "github.com/gin-gonic/gin"
)

func main() {
 r := gin.New()

 // Middleware 1 - Request validation
 r.Use(validateRequest)

 // Middleware 2 - Request tracing
 r.Use(traceRequest)

 // Middleware 3 - Security
 r.Use(secureRequest)

 // Middleware 4 - Authentication
 r.Use(authenticateRequest)

 // Login Endpoint
 r.POST("/login", login)

 if err := r.Run(":8080"); err != nil {
  fmt.Println("Failed to start server")
 }
}

func login(c *gin.Context) {
 // Authentication passed, do login logic
 c.JSON(http.StatusOK, gin.H{
  "message": "Logged in successfully",
 })
}

func validateRequest(c *gin.Context) {
 // Validate the request

 // Call the next middleware or endpoint handler
 c.Next()
}

func traceRequest(c *gin.Context) {
 // Do some request tracing before request is processed
 beforeRequest(c)

 // Call the next middleware or endpoint handler
 c.Next()

 // Do some request tracing after request is processed
 afterRequest(c)
}

func secureRequest(c *gin.Context) {
 // Add some security features to the request

 // Call the next middleware or endpoint handler
 c.Next()
}

func authenticateRequest(c *gin.Context) {
 // Authenticate the request

 // Call the next middleware or endpoint handler
 c.Next()
}

func beforeRequest(c *gin.Context) {
 start := time.Now()

 // Log the request start time
 fmt.Printf("Started %s %s\n", c.Request.Method, c.Request.URL.Path)

 // Add start time to the request context
 c.Set("startTime", start)
}

func afterRequest(c *gin.Context) {
 // Get the start time from the request context
 startTime, exists := c.Get("startTime")
 if !exists {
  startTime = time.Now()
 }

 // Calculate the request duration
 duration := time.Since(startTime.(time.Time))

 // Log the request completion time and duration
 fmt.Printf("Completed %s %s in %v\n", c.Request.Method, c.Request.URL.Path, duration)
}

Enter fullscreen mode Exit fullscreen mode

In this example, we’re adding four middleware functions to our Gin application using the Use() method. Each middleware function is defined as a gin.HandlerFunc that takes a gin.Context object as a parameter.

The order in which we add middleware using the Use() method matters. Middleware functions are executed in the order they are added, so it's important to add them in the correct order to ensure that the desired behavior is achieved.

In TraceRequest middleware, we added two functions: beforeRequest and afterRequest .we first call beforeRequest function to do some tracing before the request is processed. After the request is processed, we call afterRequest function to do some tracing after the request is processed.

Inside beforeRequest function, we log the start time of the request and add it to the request context. Inside afterRequest function, we get the start time from the request context, calculate the request duration, and log the completion time and duration.

*Sequence diagram:
*

Image description

Conclusion
In this article, we have seen how middleware works in Go and why it is important to use them in web applications. We have also seen some common middleware use cases such as request validation, request tracing, security, and authentication. By using middleware, we can write modular and reusable code, and easily add functionality to our application without changing the core logic.

In summary, if you are building a web application in Go, middleware is an essential concept that you should master to improve the quality and security of your code.

I hope you found this article helpful in understanding middleware concepts. If you enjoyed reading this, please like the article and consider following me for more Go programming tutorials.

Latest comments (0)