How to Serve Static Files with Golang?

Welcome to the next part of the journey with Golang programming language. For those of you who are entirely new to Golang, I advise you to go to the official “Getting started” tutorial, which you can find here, or to my previous post about Golang: An introduction to programming in Golang.

There is no doubt that learning by doing is better than digging up documentation and trying to remember as much as possible. With this assumption, I would like to show you how to make something up and running in Golang. We will create a simple “app”, which will serve a different kind of files with respect to their types.

Creating App’s Architecture

First things first. To get started, create a new folder for a project and a few subfolders in the folder where you keep your Golang programs:

mkdir golang-blog && cd golang-blog
mkdir src && mkdir bin && mkdir pkg
cd src && mkdir main
cd main && touch main.go

When you create your Go applications, you should always remember about keeping a folder structure in the same manner – this is a Go convention. It is worth to get familiar with a presentation that deals with organizing Go code. You can find it here.

Back to the topic. After you execute all the commands concerning folder structure in your terminal, open your main.go in your favorite IDE. We will now create some simple server for serving our code in a browser.


Let me now explain what is going on in the code. We are importing a net/http standard library package. Quoting official documentation: Package http provides HTTP client and server implementations. The http.HandleFunc is used to add some handlers to a DefaultServeMux. In our case someFunc is our handler.
For those who don’t know, handlers are responsible for writing response headers and bodies. As long as some code satisfies the http.Handler interface, it can be a handler. Or, in other words, a ServerHTTP method must be specified in that code with the following signature:

ServeHTTP(http.ResponseWriter, *http.Request)

We use forward slash / in http.HandleFunc for URL requests. We are simply telling Go to match every request to the most specific route registered. So if somebody navigates to the browser with your GO app running and tries to navigate to /IamAwesome, then this request will be matched with the closest defined, which in this case is /.
Then there is http.ListenAndServe which starts an HTTP server with a given address and handler. It listens on port 8000 and nil means we will be using DefaultServerHTTP. You can find more about this package, in the official documentation.
Moving on. There is func someFunc which takes the writer, w , and writes back some request with http.ResponseWriter to the user. The req in req *http.Request is the incoming request. w.Write([]byte("Hello universe")) means that for the proper response to the incoming request, it dumps a string (or just bytes).
Now, from your project main folder, use:

go run src/main/main.go

and navigate to localhost:8000 to see a message.
It is very important that you remember to use the command go run from the main folder which is the root of the application. In our case, for now, everything will be just fine. That is because all of the code sits in main.go. But if our project grows, there will be much more code in many folders, e.g. templates folder with some HTML. Running go from some subfolder will make it unable to get all the files outside that folder.

Bonus: Adding a Custom “MUX”

Previously, we were using a DefaultServerHTTP. Now we are creating a custom ServeMUX which is myMux := http.NewServeMux(). Creating your own ServeMUX gives you more flexibility and of course is fun. We can say that ServeMUX is an HTTP request router. It compares incoming requests to the list of predefined URL paths and fires up associated handlers for this path.

There are plenty of ServeMUX or so-called multiplexors or muxes to choose from. Just to mention a very popular Gorilla Mux. There is a nice article about ServeMuxes and handlers, and you can find it here – worth reading.

Methods Are Calling


Take a second and look at the gist. Fire it up and see what is happening now. Note that we are using nil which stands for DefaultServeMUX again.
It is worth mentioning that GO is case sensitive – using small or capital letters matters A LOT. When we are defining type person struct, it becomes a private type. It is accessible only in a package where it was introduced. If we wrote type Person struct, then it would become a public type. It would be accessible from any fragment of our code after being imported into the package.
A personOne is an instance (Object Oriented word – not commonly used in Golang) of a type.
The struct in GO is a typed collection of fields which are useful when grouping data to form some kind of records. In our case, we have a field someName and it takes a string as a value.
The (p *person) ServeHTTP means that we are attaching to any type of person (p *person), a struct ServeHTTP method. In other words, the ServeHTTP has a receiver of any person and any person is receiving a ServeHTTP method. If we delete (p *person) then our func ServeHTTP will end up as a function. In our case, it is a method of any person struct.

Serving Some Files

In this step, we are adding two more packages. io/ioutil which we will use for some input/output actions. We will be using log for logging some errors or successes.

First, we define MyHandler struct (private or public) with nothing in it. It is receiving ServeHTTP method.

Then we are defining a method func (this *MyHandler) ServeHTTP(w http.ResponseWriter, r *http.Request).What is its job? For each HTTP request, r *http.Request, it gets a path from URL and then it slices it, beginning at forward slash r.URL.Path[1:] and prints data from path in the console log.Println(path).

The := construct is a shorthand for initializingvar path string = r.URL.Path[1:] This shorthand style can only be used inside a function.

Now we have to deal with data and errors. We initialize variable data and err. The assigned ioutil.ReadFile(string(path)) is responsible for reading any data or err that it receives on a given path and dumping it to a terminal.

Next, we make a condition when no error occurs – if err == nil. If there is no error, it will respond to the request with a received data – w.Write(data). If else, it will write a message with a 404 status – http.StatusText(404).
Now, to see what will happen if there is some real file on a given URL:

mkdir templates
cd templates
touch home.html
cd ..
mkdir public && cd public
mkdir css && cd css
touch app.css

Edit the home.html and app.css files and add some basic stuff:


Now, run the app from the root folder and navigate to:

localhost:8000/templates/home.html

Everything is great but… Why didn’t it serve the stylesheet? Well, if you open your developer’s tools console, you’ll find this message:

Resource interpreted as Stylesheet but transferred with MIME type text/plain

When you switch to Network and click app.css, you will find out that the Response Headers/ Content-Type for stylesheet is text/plain. Simply, the browser has no idea how to parse this file.

Setting Content Type


Now we will handle the problem of dynamically setting “Content-Type” based on a type of the file which will be served.

First of all, I added a package strings which tests whether the string ends with the declared suffix.

I added a variable contentType string. I’m just declaring it – no assignment.

Next, I make a condition if strings.HasSuffix which checks if a string assigned to the variable path ends with i.e. .css. Then, if a path ends with .css I assign contentType text/css. I am looping over a few types and if none of them are true, I assign .

In the end, I am adding the specific content type to the header Add('Content Type', contentType) and then return the data of any file I open.
Now you can start your Go server again, refresh a browser and see that everything works fine there. Open Developer Tools and check Network. You will see that now files have the appropriate content type.

That is all for this time. I am very glad that I can share my knowledge with you. Go further with the ideas shown above and have a lot of fun.

An Introduction to Programming in Golang

A compilation is a process where the source code you wrote translates into a lower-level language – the language that can be understood by a machine. Compilation can take long minutes, but with Golang it’s different… Read more

Wood coffee Iphone Notebook

Selenium Integrated Development Environment (IDE) is a Firefox plugin which makes the testers’ life easier. It was developed by Shinya Kasatani and became a part of Selenium family (IDE, RC, WebDriver, and Grid) in 2006… Read more

How to deliver IT projects successfully?

It seems to be quite a challenge to deliver IT projects on time, doesn’t it? We always work until the last minute. Things break at the most unexpected moment. Sounds like something you already know?… Read more

Share the article with your friends!

Written by:

Junior Front-end Developer. Fast learner with the ability to explain tech-related problems to the non-tech people.