Using Go to Serve a Single Page Web App and an API

Fri Nov 25, 2016 using tags go , reactjs , emberjs

If you can’t figure out how to use Go to serve your single page web app as well as handle your API, have no fear. I couldn’t really find any information on this, so I figured I’d post this to help one of you out!

So my project directory is structured like this:

|app.go
|dist/
|- - - index.html
|- - - bundle.js
|- - - bundle.css

Lets set up app.go so that that it serves our web app:

package main

import (
	"log"
	"net/http"
	"regexp"

	"github.com/gorilla/mux"
)

// StaticHandler : Handles static files
func StaticHandler(w http.ResponseWriter, r *http.Request) {
	extension, _ := regexp.MatchString("\\.+[a-zA-Z]+", r.URL.EscapedPath())
	// If the url contains an extension, use file server
	if extension {
		http.FileServer(http.Dir("./dist/")).ServeHTTP(w, r)
	} else {
		http.ServeFile(w, r, "./dist/index.html")
	}
}

func main() {
	log.Println("Starting Server")

	r := mux.NewRouter()
	api := r.PathPrefix("/api").Subrouter()
	// api.HandleFunc("/users", UserHandler).Methods("GET")
	r.PathPrefix("/").HandlerFunc(StaticHandler)

	http.Handle("/", r)

	log.Println("Listening on 3001")
	http.ListenAndServe(":3001", nil)
}

What is going on in this file?

Sets up the API routes

api := r.PathPrefix("/api").Subrouter()
// api.HandleFunc("/users", UserHandler).Methods("GET")

Sets up a “catch-all” handler

r.PathPrefix("/").HandlerFunc(StaticHandler)

Adding this to the router makes StaticHandler handle every request that wasn’t explicitly defined (the API)

Handles static files

func StaticHandler(w http.ResponseWriter, r *http.Request) {
    extension, _ := regexp.MatchString("\\.+[a-zA-Z]+", r.URL.EscapedPath())
    // If the url contains an extension, use file server
    if extension {
        http.FileServer(http.Dir("./dist/")).ServeHTTP(w, r)
    } else {
        http.ServeFile(w, r, "./dist/index.html")
    }
}

This is the function you did not know how to write. It’s ok. I have your back.

StaticHandler checks if the path requested has an extension (“/bundle.js”, “/bundle.css”, etc). If it does, then a FileServer is made, which effectively causes the requested file to be served. If the path requested does not have an extension (“/about”, “/app”, etc.) then index.html is served. This is what allows react-router, ember router, etc. to work.


That’s it. Have fun writing some Go powered apps!