Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add documentation for using mux to serve a SPA #493

Merged
merged 5 commits into from Jul 20, 2019
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
72 changes: 72 additions & 0 deletions README.md
Expand Up @@ -26,6 +26,7 @@ The name mux stands for "HTTP request multiplexer". Like the standard `http.Serv
* [Examples](#examples)
* [Matching Routes](#matching-routes)
* [Static Files](#static-files)
* [Serving SPAs](#serving-spas)
fharding1 marked this conversation as resolved.
Show resolved Hide resolved
* [Registered URLs](#registered-urls)
* [Walking Routes](#walking-routes)
* [Graceful Shutdown](#graceful-shutdown)
Expand Down Expand Up @@ -212,6 +213,77 @@ func main() {
}
```

### Serving SPAs

Most of the time it makes sense to serve your SPA on a separate web server from your API,
but sometimes it's desirable to serve them both from one place. It's possible to write a simple
handler for serving your SPA (for use with React's BrowserRouter for example), and leverage
fharding1 marked this conversation as resolved.
Show resolved Hide resolved
mux's powerful routing for your API endpoints.

```go
package main

import (
"encoding/json"
"log"
"net/http"
"os"
"path/filepath"
"time"

"github.com/gorilla/mux"
)

type spaHandler struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be more accessible to make this middleware or more self-contained http.HandlerFunc?

e.g.

func ServeJavaScriptApp(staticDir string, entryPoint string) http.Handler {
    // fn := func(w http.ResponseWriter, r *http.Request) { ... }
    //
    // return http.HandlerFunc(fn)
}

... and then:

serveJSHandler := ServeJavaScriptApp("./dist", "index.html")
r.Use(staticHandler)
r.PathPrefix("/").Handler(serveJSHandler)

Not tied to this - just might be more accessible to a newcomer? Open to feedback on this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I can see how a http.HandlerFunc might be easier for a beginner than a type implementing http.Handler, however I think the overhead of the closure might outweigh that. Overall I think both are about as easy for a beginner to understand (also open to feedback of course 😄).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So, thinking on this: should we add a “ServeStatic” or similar handler as a built-in? This has come up a few times.

staticPath string
indexPath string
}

func (h spaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
path, err := filepath.Abs(r.URL.Path)
if err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
}

path = filepath.Join(h.staticPath, path)
fharding1 marked this conversation as resolved.
Show resolved Hide resolved

_, err = os.Stat(path)
if os.IsNotExist(err) {
// file does not exist, serve index.html
http.ServeFile(w, r, filepath.Join(h.staticPath, h.indexPath))
return
} else if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}

http.FileServer(http.Dir(h.staticPath)).ServeHTTP(w, r)
}

func main() {
fharding1 marked this conversation as resolved.
Show resolved Hide resolved
r := mux.NewRouter()

r.HandleFunc("/api/health", func(w http.ResponseWriter, r *http.Request) {
// an example API handler
json.NewEncoder(w).Encode(map[string]bool{"ok": true})
})

spa := spaHandler{staticPath: "build", indexPath: "index.html"}
r.PathPrefix("/").Handler(spa)

srv := &http.Server{
Handler: r,
Addr: "127.0.0.1:8000",
// Good practice: enforce timeouts for servers you create!
WriteTimeout: 15 * time.Second,
ReadTimeout: 15 * time.Second,
}

log.Fatal(srv.ListenAndServe())
}
```

### Registered URLs

Now let's see how to build registered URLs.
Expand Down