A Go package providing a Vue-compatible web application framework. It combines common patterns and best practices into a high-level API abstraction so that you can focus on building apps.
Features:
- Routes are matched in the same way as Vue Router and supports having a single source of truth for both frontend and backend routes.
- Integrates well with Vite development by proxying requests to Vite.
- Production ready and can be exposed directly on open Internet.
- Supports HTTP2 and TLS out of the box. TLS certificates can be automatically obtained (and updated) using Let's Encrypt (when running accessible from the Internet).
- Efficient serving of static files from memory with compression, caching, and HTTP range requests.
- Makes canonical log lines for each request.
- Supports metrics in context and server timing measurements in response header.
- Supports structured metadata in a response header encoded based on RFC 8941.
- Supports web sockets.
- Can serve multiple sites/configurations.
- Supports CORS handling per route.
This is a Go package. You can add it to your project using go get
:
go get gitlab.com/tozd/waf
It requires Go 1.23 or newer.
Automatic media type detection uses file extensions and a file extension database has to be available
on the system.
On Alpine this can be mailcap
package.
On Debina/Ubuntu media-types
package.
See full package documentation on pkg.go.dev.
See examples to see how can components combine together into an app.
To run apps locally, you need a HTTPS TLS certificate (as required by HTTP2). When running locally you can use mkcert, a tool to create a local CA keypair which is then used to create a TLS certificate. Use Go 1.19 or newer.
go install filippo.io/mkcert@latest
mkcert -install
mkcert localhost 127.0.0.1 ::1
This creates two files, localhost+2.pem
and localhost+2-key.pem
, which you can then pass in
TLS configuration to Waf.
During development you might want to use Vite. Vite compiles frontend files and serves them. It also watches for changes in frontend files, recompiles them, and hot-reloads the frontend as necessary. Node 16 or newer is required.
After installing dependencies and running vite serve
, Vite listens on http://localhost:5173
.
Pass that to Service's Development field.
Open https://localhost:8080/ in your browser, which will connect
you to the backend which then proxies unknown requests (non-API requests) to Vite, the frontend.
If you want your handler to proxy to Vite during development, you can do something like:
func (s *Service) Home(w http.ResponseWriter, req *http.Request, _ Params) {
if s.Development != "" {
s.Proxy(w, req)
return
}
// ... your handler ...
}
You can create JSON with routes in your repository, e.g., routes.json
which you can then
use both in your Go code and Vue Router as a single source of truth for routes:
{
"routes": [
{
"name": "Home",
"path": "/",
"api": null,
"get": {}
}
]
}
To populate Service's Routes field:
import _ "embed"
import "encoding/json"
import "gitlab.com/tozd/waf"
//go:embed routes.json
var routesConfiguration []byte
func newService() (*waf.Service, err) {
var config struct {
Routes []waf.Route `json:"routes"`
}
err := json.Unmarshal(routesConfiguration, &config)
if err != nil {
return err
}
return &waf.Service[*waf.Site]{
Routes: config.Routes,
// ... the rest ...
}
}
On the frontend:
import { createRouter, createWebHistory } from "vue-router";
import { routes } from "@/../routes.json";
const router = createRouter({
history: createWebHistory(),
routes: routes
.filter((route) => route.get)
.map((route) => ({
path: route.path,
name: route.name,
component: () => import(`./views/${route.name}.vue`),
props: true,
})),
});
const apiRouter = createRouter({
history: createWebHistory(),
routes: routes
.filter((route) => route.api)
.map((route) => ({
path: route.path == "/" ? "/api" : `/api${route.path}`,
name: route.name,
component: () => null,
props: true,
})),
});
router.apiResolve = apiRouter.resolve.bind(apiRouter);
// ... create the app, use router, and mount the app ...
You can then use router.resolve
to resolve non-API routes and router.apiResolve
to resolve API routes.
Content negotiated responses do not cache well.
Browsers cache by path
and ignore Accept
header. This means that if your frontend requests both text/html
and
application/json
at same path only one of them will be cached and then if you then
repeat both requests, the request for non-cached content type might arrive first, invalidating
even the cached one. Browsers at least do not serve wrong content because Etag header
depends on the content itself so browsers detect cache mismatch.
There are many great projects doing similar things. Waf's primarily goal is being compatible with Vue Router and frontend development with Vite.
This package works well with gitlab.com/tozd/go/zerolog (based on zerolog) and gitlab.com/tozd/go/cli (based on Kong) packages.
There is also a read-only GitHub mirror available, if you need to fork the project there.