Build Web Server using GO [Basics to Advanced]


GO, GOLANG Solutions

In this guide, we’ll walk you through the steps to create your own web server using net/http package in Go. We plan to talk about various key parts of the golang web server, starting with simple things like handling web page requests and guiding the user to the correct page. We will then move on to more advanced stuff like middleware, which helps in organizing code better, and templates, which help in showing different content to different users.

 

Steps to create Simple Web Server using net/http package in GO

Go is quite popular because it’s strong and easy to use, making it a top choice for creating web servers. Let us further deep dive into step by step instructions to create your own web server over HTTP with some practical examples:

 

1. Understanding the Basics

HTTP and net/http Package

  • HTTP Protocol: It's the foundation of data communication on the World Wide Web, where clients (like browsers) send requests to servers, which then respond.
  • net/http Package: This Go package provides HTTP client and server implementations. It is robust and versatile, suitable for creating web applications and APIs.

Request-Response Model

  • Clients and Servers: Clients send requests (GET, POST, etc.), and servers process these requests and return responses.
  • Handlers and Routing: In Go, handlers respond to HTTP requests, and routing refers to directing a request to the correct handler based on the URL.

 

2. Setting Up Your Workspace

Install Go: Ensure Go is installed: go version. If not, download it from the Go website.

Creating a Project Directory: Create a new directory for your project and initialize it as a Go module:

mkdir my-webserver
cd my-webserver
go mod init my-webserver

Create a main file, for example, main.go, which will hold your server code.

 

3. Import Packages

Importing Packages: At the top of main.go, import net/http and fmt. Additionally we need log module for logging information.

package main

import (
    "fmt"
    "net/http"
    "log"
)

 

4. Create a Handler

Handlers in Go are responsible for handling HTTP requests. They are defined as any type that implements the http.Handler interface, which requires a single method, ServeHTTP(ResponseWriter, *Request).

4.1 Basic Handler

A basic handler can be a function that writes a response to the client.

func homePage(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Welcome to the HomePage!")
}

This handler responds to web requests at the home page. http.ResponseWriter is used to write a response back to the client, and *http.Request contains all information about the request.

If you are looking for more advanced implementation then you can consider following handler scenarios:

4.2 Handler for Different HTTP Methods

Handlers can differentiate requests based on HTTP methods like GET, POST.

func methodHandler(w http.ResponseWriter, r *http.Request) {
    switch r.Method {
    case "GET":
        fmt.Fprintf(w, "GET request received")
    case "POST":
        fmt.Fprintf(w, "POST request received")
    default:
        http.Error(w, "Invalid method", http.StatusMethodNotAllowed)
    }
}

4.3 Handling Dynamic Routes

Dynamic routes can capture variables from the URL.

Example with Gorilla Mux:

func dynamicHandler(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    id := vars["id"]
    fmt.Fprintf(w, "ID: %s", id)
}

// In main function
r := mux.NewRouter()
r.HandleFunc("/items/{id}", dynamicHandler)

4.4 Serving Static Files

Handlers can also serve static files like HTML, CSS, and images.

func main() {
    http.Handle("/", http.FileServer(http.Dir("./static")))
    http.ListenAndServe(":8080", nil)
}

4.5 JSON Response Handler

Handlers can return JSON data, useful for APIs.

type Response struct {
    Message string `json:"message"`
}

func jsonResponseHandler(w http.ResponseWriter, r *http.Request) {
    response := Response{"Hello, JSON!"}
    json.NewEncoder(w).Encode(response)
}

4.6 Advanced: Struct-Based Handlers

For more complex logic, handlers can be implemented as methods on a struct.

type HomePageHandler struct {}

func (h HomePageHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Welcome to the Struct-Attached HomePage!")
}

 

5. Setup a Router

Setting up a router in Go's net/http package involves defining how your server handles different paths (URLs) and methods (like GET or POST). The router directs incoming HTTP requests to the correct handler functions based on the URL path.

1. Using Default ServeMux with Basic Handlers:

Use http.HandleFunc to tell the http package how to handle requests to the web server:

func handleRequests() {
    http.HandleFunc("/", homePage) // Setup the root route
    log.Fatal(http.ListenAndServe(":8080", nil)) // Start the server
}

We can add more routes within handleRequests(). For instance, if we have other handler functions like aboutPage, we can add a new route in the same function:

func handleRequests() {
    http.HandleFunc("/", homePage)
    http.HandleFunc("/about", aboutPage) // Additional route
    log.Fatal(http.ListenAndServe(":8080", nil))
}

2. Creating Custom ServeMux:

For customized routing, create an instance of http.ServeMux.

mux := http.NewServeMux()
mux.HandleFunc("/", homePage) // Using HomePage handler
http.ListenAndServe(":8080", mux)

3. Using Third-Party Routers (Gorilla Mux):

Advanced routing capabilities, like handling dynamic routes.

r := mux.NewRouter()
r.HandleFunc("/items/{id}", dynamicHandler) // Dynamic route handler
http.ListenAndServe(":8080", r)

 

6. Start the server

Here is our final code:

package main

import (
    "fmt"
    "net/http"
    "log"
)

func homePage(w http.ResponseWriter, r *http.Request){
    fmt.Fprintf(w, "Welcome to the HomePage!")
}

func aboutPage(w http.ResponseWriter, r *http.Request){
    fmt.Fprintf(w, "About Page")
}

func handleRequests() {
    http.HandleFunc("/", homePage)
    http.HandleFunc("/about", aboutPage)
    log.Fatal(http.ListenAndServe(":8080", nil))
}

func main() {
    handleRequests()
}

Next we need to call the handleRequests() function from the main function:

func main() {
    handleRequests()
}

Use go run main.go in the terminal. Your server is now live at http://localhost:8080.

 

Adding Advanced Features to Golang Web Server

Now that we have a simple web server UP and Running on Golang, let us learn about some advanced features which can be incorporated inside the web server:

 

1. Using Middleware

Define Middleware Function:

  • A middleware function typically takes an http.Handler and returns a new http.Handler.
  • It can perform actions before or after calling the ServeHTTP method of the provided handler.

Integrating middleware into your Go web server involves adding a layer of code that processes requests before they reach our main handlers (homePage and aboutPage). Middleware can be used for various purposes, such as logging, authentication, request modification, or response manipulation.

Here's how we can integrate a simple logging middleware into our existing web server:

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Printf("Request received: %s %s", r.Method, r.URL.Path)
        next.ServeHTTP(w, r)
        log.Println("Request handled successfully")
    })
}

Integrate Middleware into Web Server:

  • Wrap the handlers with the middleware.
  • This can be done when setting up the routes in handleRequests.

Updated handleRequests Function:

func handleRequests() {
    http.Handle("/", loggingMiddleware(http.HandlerFunc(homePage)))
    http.Handle("/about", loggingMiddleware(http.HandlerFunc(aboutPage)))
    log.Fatal(http.ListenAndServe(":8080", nil))
}

Run the Server:

  • Execute the code using go run main.go in your command line interface (CLI).
  • Ensure your Go environment is properly set up to run the code.

Test the Endpoints:

  • Use curl or a similar tool to make requests to your server.
  • To test the home page:
curl http://localhost:8080/

Sample Output:

2024/01/12 14:52:07 Request received: GET /
2024/01/12 14:52:07 Request handled successfully

 

2. Working with templates

Templates in a Golang web server enhance the dynamic generation of content, allowing for a separation between the application logic and presentation layers. The Go programming language offers robust support for working with text and HTML templates through its text/template and html/template packages.

Here's an example of how to integrate template handling into our web server:

Import the html/template Package:

import "html/template"

Define a Template:

  • Create a new .html file, e.g., homePage.html.
  • Use Go's template syntax to make it dynamic.
  • Example content for homePage.html:
<html>
<head><title>Home Page</title></head>
<body>
  <h1>{{.Title}}</h1>
  <p>{{.Message}}</p>
</body>
</html>

Modify Your Handler to Use the Template:

  • Parse the template file and execute it to render the HTML.
  • Example for homePage handler:
func homePage(w http.ResponseWriter, r *http.Request){
    t, err := template.ParseFiles("homePage.html")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    data := struct {
        Title   string
        Message string
    }{
        Title:   "Welcome",
        Message: "This is the Home Page",
    }
    t.Execute(w, data)
}

Run Your Server and Access the Page:

  • Start your server: go run main.go.
  • Open a web browser and navigate to http://localhost:8080.

This process will render the homePage.html template dynamically, injecting the data specified in the homePage handler.

 

3. Handling Forms and User Input

Handling forms and user input is a fundamental aspect of developing web applications in a golang web server. It involves capturing data submitted by users through forms, validating this data to ensure it meets specified criteria, and then processing it for further use.

To integrate form handling into our web server, we'll add a handler that processes form submissions. Here’s how to do it:

Create a Form in HTML:

  • Create an HTML file, e.g., form.html, with a simple form.
  • Example content of form.html:
<form action="/submit-form" method="post">
  <input type="text" name="username" />
  <input type="submit" value="Submit" />
</form>

Add a Handler for Displaying the Form:

  • Serve the HTML form when the /form route is accessed.
  • Add this handler in handleRequests:
http.HandleFunc("/form", formHandler)

Implementation of formHandler:

func formHandler(w http.ResponseWriter, r *http.Request) {
    if r.Method == "GET" {
        http.ServeFile(w, r, "form.html")
    }
}

Handle Form Submission:

  • Create a handler to process form submissions.
  • Add a new route in handleRequests:
http.HandleFunc("/submit-form", submitFormHandler)

Implement submitFormHandler:

func submitFormHandler(w http.ResponseWriter, r *http.Request) {
    if err := r.ParseForm(); err != nil {
        http.Error(w, "ParseForm() error", http.StatusInternalServerError)
        return
    }
    fmt.Fprintf(w, "Received submission: %s", r.FormValue("username"))
}

Testing Form Submission:

  • Run the server: go run main.go.
  • Navigate to http://localhost:8080/form in a browser, fill out the form, and submit.
  • The submitFormHandler will process the input and display the received data.

 

4. State Management

State management is essential in maintaining user-specific data between multiple requests in a Golang web server. Cookies and sessions are two common techniques used to manage state, allowing web applications to remember users and their activities.

1. Cookies

Cookies are small pieces of data stored on the client-side, sent to the server with each request. They are useful for saving user preferences, tracking users, and more.

package main

import (
	"fmt"
	"net/http"
)

func setCookieHandler(w http.ResponseWriter, r *http.Request) {
	http.SetCookie(w, &http.Cookie{
		Name:  "username",
		Value: "john_doe",
	})

	fmt.Fprintf(w, "Cookie set in the Golang Web Server!")
}

func getCookieHandler(w http.ResponseWriter, r *http.Request) {
	cookie, err := r.Cookie("username")
	if err != nil {
		fmt.Fprintf(w, "No cookie found in this Golang Web Server.")
	} else {
		fmt.Fprintf(w, "Found a cookie in this Golang Web Server! Username: %s", cookie.Value)
	}
}

func main() {
	http.HandleFunc("/set-cookie", setCookieHandler)
	http.HandleFunc("/get-cookie", getCookieHandler)
	http.ListenAndServe(":8080", nil)
}

In this "golang web server" example, two endpoints are defined: one for setting a cookie and another for retrieving and displaying the cookie value.

2. Sessions

Sessions provide a way to store user-specific data on the server-side, tracked by a unique session identifier stored as a cookie on the client-side.

package main

import (
	"fmt"
	"net/http"
	"github.com/gorilla/sessions"
)

var store = sessions.NewCookieStore([]byte("secret-key"))

func sessionHandler(w http.ResponseWriter, r *http.Request) {
	session, _ := store.Get(r, "session-name")

	if r.Method == "POST" {
		username := r.FormValue("username")
		session.Values["username"] = username
		session.Save(r, w)
	}

	fmt.Fprintf(w, "Golang Web Server Session Value: %v", session.Values["username"])
}

func main() {
	http.HandleFunc("/session", sessionHandler)
	http.ListenAndServe(":8080", nil)
}

In this session example of a "golang web server," Gorilla Sessions, a third-party package, is used to manage sessions. A session value is stored and retrieved, representing user-specific data, such as a username.

 

5. Handling Non-existent Pages (404 Errors)

Custom error pages, such as 404 pages, help in gracefully handling non-existent pages, providing users with helpful information and navigation options.

package main

import (
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	http.Error(w, "Golang Web Server: Page not found!", http.StatusNotFound)
}

func main() {
	http.HandleFunc("/", handler)
	http.ListenAndServe(":8080", nil)
}

This example demonstrates a simple "golang web server" that responds with a custom 404 error message for non-existent pages.

 

6. Saving Pages

Saving pages could refer to saving or storing user-generated content or data submitted through forms into a database or file.

package main

import (
	"fmt"
	"net/http"
)

func saveHandler(w http.ResponseWriter, r *http.Request) {
	if r.Method == "POST" {
		r.ParseForm()
		data := r.FormValue("data")

		// In a real-world scenario, the data would be saved to a database or file.
		fmt.Fprintf(w, "Golang Web Server: Data saved - %s", data)
	}
}

func main() {
	http.HandleFunc("/save", saveHandler)
	http.ListenAndServe(":8080", nil)
}

In this "golang web server" example, an endpoint is created to save data received from a POST request.

 

7. Adding Timeouts

Adding timeouts to our Go web server enhances its resilience by preventing long-running requests from consuming server resources indefinitely. Here's how to implement it:

  1. Use http.Server Structure:
    • This allows more control over server properties, including timeouts.
  2. Setting Timeouts:
    • Implement read, write, and idle timeouts for the server.
  3. Example with Timeouts:
func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", homePage)
    mux.HandleFunc("/about", aboutPage)

    server := &http.Server{
        Addr:         ":8080",
        Handler:      mux,
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout:  15 * time.Second,
    }

    log.Fatal(server.ListenAndServe())
}

To incorporate timeouts into our existing Go web server code, we can modify the handleRequests function to utilize the http.Server struct. This allows us to set read, write, and idle timeouts for better control over server behavior. Here's the updated code:

package main

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

func homePage(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Welcome to the HomePage!")
}

func aboutPage(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "About Page")
}

func handleRequests() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", homePage)
    mux.HandleFunc("/about", aboutPage)

    server := &http.Server{
        Addr:         ":8080",
        Handler:      mux,
        ReadTimeout:  10 * time.Second,
        WriteTimeout: 10 * time.Second,
        IdleTimeout:  15 * time.Second,
    }

    log.Fatal(server.ListenAndServe())
}

func main() {
    handleRequests()
}

In this revised version, the handleRequests function creates a new http.ServeMux and assigns the handlers. The http.Server struct is then used to specify the server's address, handler, and various timeouts. This setup provides a more robust handling of HTTP connections.

 

8. Error Handling

Effective error handling improves the resilience of the "golang web server," ensuring that it responds gracefully to different error conditions.

package main

import (
	"net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
	_, err := http.Get("http://example.com/nonexistentpage")
	if err != nil {
		http.Error(w, "Golang Web Server: Error fetching page", http.StatusInternalServerError)
		return
	}

	fmt.Fprintln(w, "Golang Web Server: Page fetched successfully!")
}

func main() {
	http.HandleFunc("/", handler)
	http.ListenAndServe(":8080", nil)
}

This "golang web server" example includes error handling when fetching a page, returning an internal server error if the fetch fails.

 

Frequently Asked Questions on Golang Web Server

What is a Golang web server, and why is it popular?

A Golang web server is a server created using the Go programming language. It's popular because of its performance efficiency, scalability, and the simplicity of Go’s syntax.

How do you start a simple Golang web server?

You can start a simple Golang web server by using the http package. For example: http.HandleFunc("/", handlerFunction) and http.ListenAndServe(":8080", nil).

How do you handle routes in a Golang web server?

Handling routes involves defining URL patterns that the server will respond to and associating them with handler functions. For example, using http.HandleFunc("/example", exampleHandler).

What is middleware in a Golang web server, and how is it used?

Middleware is code that runs before the request reaches the handler. It's commonly used for logging, authentication, etc. Middleware can be applied by wrapping the handler function.

How can templates be used in a Golang web server for dynamic content?

Templates in Go, such as those provided by the text/template or html/template packages, allow for dynamic content generation by populating predefined templates with data.

How do you manage state in a Golang web server, such as sessions and cookies?

State can be managed using cookies with the http.SetCookie() function or managing sessions using third-party packages like gorilla/sessions.

How can a Golang web server handle form submissions and user input?

Form submissions and user input can be handled using the http.Request methods like ParseForm() and accessing data with FormValue("key").

What are some recommended best practices for error handling in a Golang web server?

Best practices include using custom error types, centralized error handling, and meaningful error messages, and logging errors for analysis.

How do you ensure a Golang web server is secure?

Ensure that your Golang web server is secure by using HTTPS, sanitizing user inputs, setting secure cookies, and following other web security best practices.

Which third-party libraries or frameworks are popular for building a Golang web server?

Popular third-party libraries include Gorilla Mux for routing, Negroni for middleware, and go-sql-driver/mysql for MySQL interactions.

 

Conclusion

Having journeyed through the essentials of creating a "golang web server," it is clear that Go provides a powerful and efficient platform for web development. Its standard libraries offer substantial functionalities, allowing developers to create robust, high-performance web servers and applications.

  • Routing: Efficiently handle various endpoints in your application.
  • Middleware: Implement middleware for code modularity and reusability.
  • Working with Templates: Utilize templates for dynamic content rendering.
  • Handling Forms and User Input: Ensure proper handling and validation of user input.
  • State Management: Effectively manage user sessions and cookies for personalized user experiences.
  • Error Handling: Graciously handle errors to enhance user experience and application robustness.

For deeper insights and continuous learning, the official Go documentation is a crucial resource. Here are some helpful links:

  • Go Documentation: Comprehensive details on various aspects of Go.
  • net/http package: Detailed information on building HTTP servers and clients.
  • Gorilla Mux: An efficient third-party multiplexer to bolster your routing.
  • Gorilla Sessions: A sessions management library in Go for handling user sessions.

 

Deepak Prasad

Deepak Prasad

Deepak Prasad is the founder of GoLinuxCloud, bringing over a decade of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, Networking, and Security. His extensive experience spans development, DevOps, networking, and security, ensuring robust and efficient solutions for diverse projects.

Certifications and Credentials:

  • Certified Kubernetes Application Developer (CKAD)
  • Go Developer Certification
  • Linux Foundation Certified System Administrator (LFCS)
  • Certified Ethical Hacker (CEH)
  • Python Institute PCAP (Certified Associate in Python Programming)
You can connect with him on his LinkedIn profile and join his Facebook and LinkedIn page.

Can't find what you're searching for? Let us assist you.

Enter your query below, and we'll provide instant results tailored to your needs.

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can send mail to admin@golinuxcloud.com

Thank You for your support!!

6 thoughts on “Build Web Server using GO [Basics to Advanced]”

  1. Thank you! Also, when Im commenting here and clicking “save my name and email in this browser for the next time I comment.” it just doesnt work. I need to fill it everytime. Also what I have to fill in Website placeholder? Its to tell where’s the bugs?
    But anyways, thank you again. Its my favorite website for learning.

    Reply

Leave a Comment