Michiel Mulders Michiel loves the NodeJS and Golang programming languages. A backend/core blockchain developer and avid writer, he's very passionate about blockchain technology.

Creating a web server with Golang

6 min read 1931

Creating a web server with Golang

Golang is a great language for creating simple yet efficient web servers and web services. It provides a built-in HTTP package that contains utilities for quickly creating a web or file server.

The goal of this tutorial is to create a web server that can accept a GET request and serve a response. We’ll use the server to serve static files, acting as a file server. We’ll then make the web server respond to a POST request coming from a form submission, such as a contact form.

Without further ado, let’s explore how to build your first web server with Golang.

Setup

You’ll need Go version 1.11 or higher to follow this tutorial.

In this section, we’ll create all the necessary files and establish the correct file structure. After that, we’ll import our packages to test whether the setup works. Don’t worry — the setup is very basic for the Golang web server.

Create the following files and folders according to the structure below. The file server.go sits at the root of your project, as does the static folder, which contains two HTML files: index.html and form.html.

- server.go
- static/
- - index.html
- - form.html

Now let’s write some code. Open the server.go file and import the required packages. We’ll use fmt to print useful data to the terminal and log to print fatal errors in case the web server crashes.

The net/http is the most important package. It provides all the functionality for creating an HTTP client or server implementation such as a Golang web server.

package main

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

Lastly, let’s add a simple main() function in the server.go file that prints a message to the terminal.

We made a custom demo for .
No really. Click here to check it out.

func main() {
    fmt.Printf("Starting server at port 8080\n")
}

To test the setup, start the fictive server with the following command.

go run server.go

If you followed along with the setup, you should see the following output in your terminal.

Starting server at port 8080

If all looks good, the next step is to create a web server.

Starting a web server with GET routes

At this stage, we’ll create a web server that is actually served on port 8080 and can respond to incoming GET requests.

Let’s modify the code in our main() function to start a web server at port 8080. The ListenAndServe method is exported by the http packet we imported during step one. This method allows us to start the web server and specify the port to listen for incoming requests.

Note that the port parameter needs to be passed as a string prepended by colon punctuation. The second parameter accepts a handler to configure the server for HTTP/2. However, this isn’t important for this tutorial, so we can safely pass nil as the second argument.

 func main() {
    fmt.Printf("Starting server at port 8080\n")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal(err)
    }
}

At this point, the server can start, but it still doesn’t know how to handle requests. We need to pass handlers to the server so it knows how to respond to incoming requests and which requests to accept.

We’ll use the HandleFunc function to add route handlers to the web server. The first argument accepts the path it needs to listen for /hello. Here, you tell the server to listen for any incoming requests for http://localhost:8080/hello. The second argument accepts a function that holds the business logic to correctly respond to the request.

By default, this function accepts a ResponseWriter to send a response back and a Request object that provides more information about the request itself. For example, you can access information about the sent headers, which can be useful for authenticating the request.

func main() {
    http.HandleFunc("/hello", func(w http.ResponseWriter, r *http.Request){
        fmt.Fprintf(w, "Hello!")
    })


    fmt.Printf("Starting server at port 8080\n")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal(err)
    }
}

As you can see, the handler sends a “Hello!” message as we pass this response to the ResponseWriter.

Now let’s try out this setup. Start the web server with go run server.go and visit http://localhost:8000/hello. If the server responds with "Hello!", you can continue to the next step, where you’ll learn how to add basic security to your Golang web server routes.

Add basic security to routes

It goes without saying that security is important. Let’s explore some basic strategies to enhance the security of your Go web server.

Before we do, we should take a moment to increase the readability of our code. Let’s create the helloHandler function, which holds all the logic related to the /hello request.

func helloHandler(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path != "/hello" {
        http.Error(w, "404 not found.", http.StatusNotFound)
        return
    }

    if r.Method != "GET" {
        http.Error(w, "Method is not supported.", http.StatusNotFound)
        return
    }


    fmt.Fprintf(w, "Hello!")
}

This handler uses the Request object to check whether the requested path is correct. This is a very basic example of how you can use the Request object.

If the path is incorrect, the server returns a StatusNotFound error to the user. To write an error to the user, you can use the http.Error method. Notice that the StatusNotFound code corresponds to a 404 error. All status codes can be found in the Golang documentation.

Next, we add a check for verifying the type of the request. If the method doesn’t correspond to GET, the server returns a new error. When both checks pass, the server returns its success response "Hello!".

The last thing we need to do is modify the handleFunc function in our main() function to accept the above helloHandler function.

http.HandleFunc("/hello", helloHandler)

Below is the full code for your server.go file.

package main


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

func helloHandler(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path != "/hello" {
        http.Error(w, "404 not found.", http.StatusNotFound)
        return
    }

    if r.Method != "GET" {
        http.Error(w, "Method is not supported.", http.StatusNotFound)
        return
    }


    fmt.Fprintf(w, "Hello!")
}


func main() {
    http.HandleFunc("/hello", helloHandler) // Update this line of code


    fmt.Printf("Starting server at port 8080\n")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal(err)
    }
}

Next, we’ll start the Go web server with go run server.go. You can test your security by sending a POST request to http://localhost:8080/hello using a tool such as Postman or cURL.

Start a static web server

In this step, we’ll create a simple file server to host static files. This will be a very simple addition to the web server.

To make sure we have content to serve on the web server, let’s modify the index.html file located in the static folder. To keep things simple, just add a heading to the file that says “Static Website.” If you wish, you can add more files or styling files to make your web server look a bit nicer.

<html>
  <head>
    <title>Static Website</title>
  </head>
  <body>
    <h2>Static Website</h2>
  </body>
</html>

To serve the static folder, you’ll have to add two lines of code to server.go. The first line of code creates the file server object using the FileServer function. This function accepts a path in the http.Dir type. Therefore, we have to convert the string path “./static” to an http.Dir path type.

Don’t forget to specify the Handle route, which accepts a path and the fileserver. This function acts in the same way as the HandleFunc function, with some small differences. For more on the FileServer object, check the documentation.

func main() {
    fileServer := http.FileServer(http.Dir("./static")) // New code
    http.Handle("/", fileServer) // New code
    http.HandleFunc("/hello", helloHandler)


    fmt.Printf("Starting server at port 8080\n")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal(err)
    }
}

It’s time to try out the code. Fire up the server with go run server.go and visit http://localhost:8080/. You should see the “Static Website” header.

Accept a form submission POST request

Lastly, the web server has to respond to a form submission.

Let’s add some content to the form.html file in the static folder. Notice that the form action is sent to /form. This means the POST request from the form will be sent to http://localhost:8080/form. The form itself asks for input for two variables: name and address.

<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8" />
</head>
<body>
<div>
  <form method="POST" action="/form">     
      <label>Name</label><input name="name" type="text" value="" />
      <label>Address</label><input name="address" type="text" value="" />
      <input type="submit" value="submit" />
  </form>
</div>
</body>
</html>

The next step is to create the handler to accept the /form request. The form.html file is already served via the FileServer and can be accessed via http://localhost:8080/form.html.

First, the function has to call ParseForm() to parse the raw query and update r.PostForm and r.Form. This will allow us to access the name and address values via the r.FormValue method.

At the end of the function, we write both values to the ResponseWriter using fmt.Fprintf.

func formHandler(w http.ResponseWriter, r *http.Request) {
    if err := r.ParseForm(); err != nil {
        fmt.Fprintf(w, "ParseForm() err: %v", err)
        return
    }
    fmt.Fprintf(w, "POST request successful")
    name := r.FormValue("name")
    address := r.FormValue("address")

    fmt.Fprintf(w, "Name = %s\n", name)
    fmt.Fprintf(w, "Address = %s\n", address)
}

Don’t forget to add the new form handler route to the main() function.

http.HandleFunc("/form", formHandler)

Now, the full code looks like this.

package main


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

func formHandler(w http.ResponseWriter, r *http.Request) {
    if err := r.ParseForm(); err != nil {
        fmt.Fprintf(w, "ParseForm() err: %v", err)
        return
    }
    fmt.Fprintf(w, "POST request successful")
    name := r.FormValue("name")
    address := r.FormValue("address")
    fmt.Fprintf(w, "Name = %s\n", name)
    fmt.Fprintf(w, "Address = %s\n", address)
}

func helloHandler(w http.ResponseWriter, r *http.Request) {
    if r.URL.Path != "/hello" {
        http.Error(w, "404 not found.", http.StatusNotFound)
        return
    }

    if r.Method != "GET" {
        http.Error(w, "Method is not supported.", http.StatusNotFound)
        return
    }


    fmt.Fprintf(w, "Hello!")
}


func main() {
    fileServer := http.FileServer(http.Dir("./static"))
    http.Handle("/", fileServer)
    http.HandleFunc("/form", formHandler)
    http.HandleFunc("/hello", helloHandler)


    fmt.Printf("Starting server at port 8080\n")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal(err)
    }
}

Trying out the form handler

We can test the form by starting the server with go run server.go. When the server starts, visit http://localhost:8080/form.html. You should see two input fields and a submit button.

Form Handler

When you’ve filled out the form, hit the submit button. The server should process your POST request and show you the result on the http://localhost:8080/form response page such as the below response.

Form Result

If you see the result above, you’ve successfully created your first Golang web and file server. Congratulations!

If you want to explore Golang web servers further, the Golang HTTP package documentation is full of great examples. This tutorial on writing web apps in Go is another great resource that covers most of the basics.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Michiel Mulders Michiel loves the NodeJS and Golang programming languages. A backend/core blockchain developer and avid writer, he's very passionate about blockchain technology.

Leave a Reply