/ code

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

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!