Go 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 Go.
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.
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.
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:8080/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.
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.
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.
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) } }
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.
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.
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.
Install LogRocket via npm or script tag. LogRocket.init()
must be called client-side, not
server-side
$ npm i --save logrocket // Code: import LogRocket from 'logrocket'; LogRocket.init('app/id');
// Add to your HTML: <script src="https://cdn.lr-ingest.com/LogRocket.min.js"></script> <script>window.LogRocket && window.LogRocket.init('app/id');</script>
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowThe useReducer React Hook is a good alternative to tools like Redux, Recoil, or MobX.
Node.js v22.5.0 introduced a native SQLite module, which is is similar to what other JavaScript runtimes like Deno and Bun already have.
Understanding and supporting pinch, text, and browser zoom significantly enhances the user experience. Let’s explore a few ways to do so.
Playwright is a popular framework for automating and testing web applications across multiple browsers in JavaScript, Python, Java, and C#. […]
8 Replies to "Creating a web server with Golang"
“package main” is outside the prettyprinted code block
Thanks for the heads-up. All set.
“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” port should be 8080 not 8000
Thanks for the great tutorial! I loved getting to see how to use POST too!
Hi Will, at the section “Accept a form submission POST request”, you can find an example with a form request.
Here’s a more concrete example:
“`
package main
import (
“log”
“net/http”
“io/ioutil”
)
func postRoute(rw http.ResponseWriter, req *http.Request) {
body, err := ioutil.ReadAll(req.Body)
if err != nil {
panic(err)
}
// further handle request
}
func main() {
http.HandleFunc(“/postRoute”, postRoute)
log.Fatal(http.ListenAndServe(“:8080”, nil))
}
“`
You can find the Gist here: https://gist.github.com/michielmulders/8c32a6978ec7829865608cd631e83c39
Hope that helps! đź‘Ť
Hello Michiel,
Thanks for the tutorial. I should say awesome tutorial. It’s the first one that actually works ans is so well explained.
I needed something this simple to get my project started. Thank you so much. Would love to see more small simple examples that takes this further.
Michiel – hard to describe how useful this was! Tutorials on the web just used either handle or handlefunc, leaving it unclear which does what and when to choose one over the other. Reading your article made it very clear.