Skip to content

Commit

Permalink
docs: add readme for SPA
Browse files Browse the repository at this point in the history
  • Loading branch information
ravisuhag committed Nov 8, 2024
1 parent c238b6e commit 66b084a
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 5 deletions.
96 changes: 96 additions & 0 deletions spa/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# SPA Server Package

The `spa` package provides a simple HTTP handler to serve Single Page Applications (SPAs) from an embedded file system, with optional gzip compression. This is particularly useful for applications that need to serve static assets and handle client-side routing, where all paths should fall back to an `index.html` file.

## Features

- **Serve Embedded Static Files**: Serve files directly from an embedded filesystem (`embed.FS`), making deployments easier.
- **SPA Support with Client-Side Routing**: Automatically serves `index.html` when a requested file is not found, allowing for client-side routing.
- **Optional Gzip Compression**: Optionally compresses responses with gzip for clients that support it.

## Installation

Add the package to your Go project by running:

```bash
go get github.com/raystack/spa
```

## Usage

Here’s an example of using `spa` to serve a Single Page Application from an embedded file system.

### Embed Your Static Files

Embed your static files (like `index.html`, JavaScript, CSS, etc.) using Go’s `embed` package:

```go
//go:embed all:build
var content embed.FS
```

### Setting Up the Server

Use the `Handler` function to create an HTTP handler that serves your SPA with optional gzip compression.

```go
package main

import (
"embed"
"log"
"net/http"

"github.com/raystack/spa"
)

//go:embed all:build
var content embed.FS

func main() {
handler, err := spa.Handler(content, "build", "index.html", true)
if err != nil {
log.Fatalf("failed to initialize SPA handler: %v", err)
}

http.Handle("/", handler)
log.Println("Starting server on :8080")
if err := http.ListenAndServe(":8080", nil); err != nil {
log.Fatalf("server failed: %v", err)
}
}
```

In this example:
- `content`: Embedded filesystem containing the build directory.
- `"build"`: The directory within the embedded filesystem where the static files are located.
- `"index.html"`: The fallback file to serve when a requested file isn’t found, typically used for client-side routing.
- `true`: Enables gzip compression for supported clients.

## API Reference

### `Handler`

```go
func Handler(build embed.FS, dir string, index string, gzip bool) (http.Handler, error)
```

Creates an HTTP handler to serve an SPA with optional gzip compression.

- **Parameters**:
- `build`: The embedded file system containing the static files.
- `dir`: The subdirectory within `build` where static files are located.
- `index`: The fallback file (usually "index.html") to serve when a requested file isn’t found.
- `gzip`: If `true`, responses will be compressed with gzip for clients that support it.

- **Returns**: An `http.Handler` for serving the SPA, or an error if initialization fails.

### `router`

The `router` struct is an HTTP file system wrapper that prevents directory traversal and supports client-side routing by serving `index.html` for unknown paths.

## Example Scenarios

- **Deploying a Go-Based SPA**: Use `spa` to embed and serve your frontend from within your Go binary.
- **Supporting Client-Side Routing**: Serve a fallback `index.html` page for any route that doesn't match an existing file, supporting SPAs with dynamic URLs.
- **Optional Compression**: Enable gzip for production deployments to reduce bandwidth usage.
19 changes: 14 additions & 5 deletions spa/spa.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,21 @@ import (
"github.com/NYTimes/gziphandler"
)

// Handler return a file server http handler for single page
// application. This handler can be mounted on your mux server.
// Handler returns an HTTP handler for serving a Single Page Application (SPA).
//
// If gzip is set true, handler gzip the response body, for clients
// which support it. Usually it also can be left to proxies like Nginx,
// this method is useful when that's undesirable.
// The handler serves static files from the specified directory in the embedded
// file system and falls back to serving the index file if a requested file is not found.
// This is useful for client-side routing in SPAs.
//
// Parameters:
// - build: An embedded file system containing the build assets.
// - dir: The directory within the embedded file system where the static files are located.
// - index: The name of the index file (usually "index.html").
// - gzip: If true, the response body will be compressed using gzip for clients that support it.
//
// Returns:
// - An http.Handler that serves the SPA and optional gzip compression.
// - An error if the file system or index file cannot be initialized.
func Handler(build embed.FS, dir string, index string, gzip bool) (http.Handler, error) {
fsys, err := fs.Sub(build, dir)
if err != nil {
Expand Down

0 comments on commit 66b084a

Please sign in to comment.