The Go language's standard library comes complete with components for creating "web applications." This is one of the things that attracted me to Go in the first place. So, today we will cover the basics needed to create a simple web-server in Go.

This tutorial uses the Atom code editor based development environment from the previous tutorial. If you've already got your Go dev. environment set up, then continue below. Otherwise you should go through the previous tutorial first.

The Simplest Web Server Ever

Create a new folder inside scratchpad (or some other folder) called "helloserver." Then, create a new file called "helloserver.go," and enter the following code:

package main

import (
	"fmt"
	"net/http"
)

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

func main() {
	// Set up the HTTP request handler
	http.HandleFunc("/", hello)

	// Run the server
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Printf("HTTP stopped with message: %v", err)
	}
}

Yes, those few lines of code are a complete web server (albeit a rather pointless one). The server is set up in two lines of code. First, a request handler is set up:

http.HandleFunc("/", hello)

This tells the http package to route all requests starting with "/" (i.e., all requests) to the hello() function. Next, the server is started on port 8080 as follows:

err := http.ListenAndServe(":8080", nil)

The hello() function itself is pretty self-explanatory. It simply sends "Hello World!" back to the web-browser that made the request.

Okay, that's enough theory for now. Let's test the server. Select Packages => Script => Run Script from the menu, or use the keyboard shortcut: Ctrl+shift+B. This will compile the server, and start it. Now, open a web browser, and enter the following in the address bar: http://localhost:8080/

After pushing enter, you should see "Hello World!" printed at the top of an otherwise empty webpage (see below). The server is working!

Simplest Go Web Server Hello

NOTE: It can take several seconds for the code to be compiled and executed. So if you got an "Unable to connect" error, then you may need to be a bit more patient. One simple trick is to insert fmt.Printf("Starting Server\n") at the top of main(). This will print "Starting Server" in the console window when it starts, and you'll know that the compilation is complete.

How This Works

If you're unfamiliar with how the "World Wide Web" (www) operates, here's a really brief overview. When you enter a website address into the web-browser, it sends a request to the server using the HyperText Transfer Protocol (HTTP). In HTTP terms, the address is a Uniform Resource Locator (URL). The server interprets the request, and sends a response back to the browser. If the URL was a valid page, then the response will contain that page.

So, when we entered http://localhost:8080/ into the browser, the following happened:

  • The web-browser sent a GET request to the server located at localhost:8080 (localhost meaning "this computer") for the resource "/"
  • Our server received the request because it was listening on port 8080
  • The request was routed through to the hello() function, which sent "Hello World!" back to the web-browser
  • The browser displayed "Hello World!"

An Improved Server

It was pretty easy to create the "Hello World" server. However, it's pretty useless and doesn't even output proper HTML (the "HyperText" part of HTTP). So, let's improve the server so that it at least generates a proper HTML page.

Shutting Down the Old Server

Before continuing we need to shut down the server that we started above. One weird thing about Go's built in HTTP server, is that it has no built-in shutdown (it doesn't respond to the usual Ctrl+C or SIGINT signal). So, it has to be forced to shut down. On windows, hit CTRL+ALT+DEL, click on "Task Manager," find "helloserver.exe" in the task list, and select "End Task." Linux users should be able to kill the server using the following command: pkill helloserver

Test that the server is shutdown quickly, by visiting http://localhost:8080/ again. Your browser will eventually give up, and say that it's unable to connect.

The Templates Module

We're going to use Go's template module to store our "Hello World" page's code. Is using a templating system overkill for a single static webpage overkill? Absolutely! However, our ultimate goal is to write a web application that users can interact with, and that's where the true power of templates comes into play. Right now we're just "getting our feet wet" in preparation for putting it to good use later.

Create a new sub-folder within helloserver called "templates." Inside that, create a file called "helloworld.html" and enter the following:

<!DOCTYPE html>
<html>
  <head>
    <title>Hello World!</title>
  </head>
  <body>
    <h1>Hello World!</h1>
    <p>Welcome to this website.</p>
  </body>
</html>

In brief: The <!DOCTYPE html> tag indicates that this is an HTML file. The actual HTML content is stored between the opening and closing html tags, <html> and </html>. HTML files usually have a head section containing things such as the page's title and other metadata. Next comes the body section, which contains the guts of the HTML page; this is the part that's displayed in the browser window. The other tags have the following meaning:

  • <h1> - A major heading (e.g., for the page title)
  • <p> - A paragraph

That's all you need to know for now. 

Now, let's update the server code (in helloserver.go). Start by inserting "html/template" into the import() section:

import (
	"fmt"
	"html/template"
	"net/http"
)

Next, replace the hello() function with:

func hello(w http.ResponseWriter, r *http.Request) {
	tplName := "templates/helloworld.html"
	tpl, err := template.ParseFiles(tplName)
	if err != nil {
		returnServerError500(w, r, "Template not found")
		return
	}
	tpl.Execute(w, nil)
}

Looking at the new hello() code step-by-step, it first loads and parses the file templates/helloworld.html:

tplName := "templates/helloworld.html"
tpl, err := template.ParseFiles(tplName)
if err != nil {
	http.Error(w, err.Error(), http.StatusInternalServerError)
	return
}

 Next, it "executes" the template file, and sends the output to the client/browser:

tpl.Execute(w, nil)

Normally, templates contain fields where dynamic content is inserted. So "executing" the template means generating the final page by inserting content into those fields. Helloworld.html has no such fields, so its content simply passes straight through, and out to the browser.

The Final Code

Putting it all together, the final helloserver.go code becomes:

package main

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

/** Hello world HTTP handler.
 */
func hello(w http.ResponseWriter, r *http.Request) {
	tplName := "templates/helloworld.html"
	tpl, err := template.ParseFiles(tplName)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	tpl.Execute(w, nil)
}

func main() {
	// Set up the HTTP request handler
	http.HandleFunc("/", hello)

	// Run the server
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Printf("HTTP stopped with message: %v", err)
	}
}

Run the new code (Packages => Script => Run Script from the menus, or Ctrl+Shift+B). Visiting http://localhost:8080/ again, you'll see a much nicer page (below).

UPDATE 2016/07/14: A recent Atom.io's script package update (3.8.1) incorrectly sets the Current Working Directory (CWD) so it can't find the templates directory. If you experience this failure then set the CWD explicitly via Packages => Script => Configure Script. You must us an absolute path (e.g., "C:/Users/Hans/GoWork/src/scratchpad/helloserver"). Hopefully they'll fix this soon.

Simple Server With HTML Hello

Final Comments

That's all for now. We have created a basic web server that outputs a simple HTML page.

Please note that the server that we created is rather inefficient. The hello() function loads and parses the helloworld.html template every time that someone requests a page. That's no big deal right now, but it will become a problem when you write an app that has to serve many people. It would be much better to cache the parsed templates for rapid access. However, that's a project for later. If you don't want to wait, then take a peek at: https://golang.org/doc/articles/wiki/#tmp_10