Vanilla JS x Go
A vanilla-JS frontend (no framework) talking to a Go backend that records audit events via sdk-go and serves a ~12 KB gzipped bundle from a single self-contained binary. Smallest end-to-end example. Comes in single-tenant and multi-tenant variants.
Source: https://github.com/everscribe/examples/tree/main/vanilla-js-fe-with-go-be
Stack
| Layer | What it is |
|---|---|
| Frontend | Plain HTML + ES modules + CSS, Vite for bundling, @everscribe/components-element |
| Backend | Go 1.25+, net/http, sdk-go, //go:embed all:web/dist |
| Domain | In-memory secrets vault |
| Audit panel | <audit-trail> custom element |
If you want the simplest possible "what does the integration look like end-to-end" reference, start here.
Run it
git clone https://github.com/everscribe/examples
cd examples/vanilla-js-fe-with-go-be/single-tenant # or .../multi-tenant
cp .env.example .env # EVERSCRIBE_PROJECT_ID + EVERSCRIBE_API_KEY
cd web && npm install && npm run build && cd ..
make build && make run
Binary on :8080 serves the bundle and the API.
Backend integration
Identical to the other Go variants — the backend doesn't change with the frontend:
import (
everscribe "github.com/everscribe/sdk-go"
"github.com/everscribe/sdk-go/pkg/event"
"github.com/everscribe/sdk-go/pkg/recorder"
)
es, _ := everscribe.New(cfg.ProjectID, cfg.APIKey)
rec := es.NewRecorder(recorder.WithFlushInterval(2*time.Second))
defer rec.Close()
m := es.NewMinter()
Recording per request:
defer func() { _ = rec.Record(r.Context(), event.Event{
Action: "secret.reveal",
Actor: actor,
Target: event.Target{Type: "secret", ID: r.PathValue("id")},
}) }()
See React + Go → Backend integration for the mint endpoint.
Frontend integration
The whole thing is a few lines of plain HTML + JS:
<!doctype html>
<link rel="stylesheet" href="/style.css">
<link rel="stylesheet" href="https://unpkg.com/@everscribe/components-styles/default.css">
<main id="vault"></main>
<audit-trail token-endpoint="/api/embed-token"></audit-trail>
<script type="module">
import "@everscribe/components-element";
// ... vault UI code
</script>
For multi-tenant customer scoping, attach the demo auth header via onTokenExpired:
const el = document.querySelector("audit-trail");
el.onTokenExpired = async () => {
const res = await fetch("/api/embed-token/customer", {
credentials: "include",
headers: { "X-Demo-Actor": currentUser.id },
});
return (await res.json()).token;
};
Real apps swap X-Demo-Actor for whatever auth your real backend expects.
Single-Tenant vs Multi-Tenant
| Single-tenant | Multi-tenant | |
|---|---|---|
| Token endpoint | One — /api/embed-token |
Two — /api/embed-token/customer, /api/embed-token/admin |
| UI | One vault view | Tab strip: Customer view · Admin view |
| Seeded data | One implicit tenant | Acme, Initech |
| Audit-panel scope | Whole project | Customer: tenant-scoped · Admin: unscoped |
What to take away
- No framework needed. The audit-trail component is a custom element — drop the tag and it works.
- The Go binary is self-contained.
make buildproduces one executable; deploy it anywhere a single binary works. - The backend is identical to the React and Svelte variants. Frontend choice is local; backend doesn't care.