Vanilla-light is a no-build, dependency-free full-stack framework with a reactive browser client and an HTTPS Bun server.
Why I built this
This is the culmination of 20 years of writing, re-writing, imagining, and re-imagining what is the minimal core framework and best abstraction of the web client/server model.
Web standards give us all the tools we need to create applications. Removing complexity and the sheer number of things you have to learn makes them easier to build.
Most apps are really not that complex. They consist of a front-end, back-end server, auth, storage, and (lately) communication with llms. Sophisticated server and client rendering schemes, bloated thousand-dependency builds, and quirky frame dependent abstractions do not belong here.
Vanilla light is intended to be an easy to adopt minimal core for humans to build things quickly and go from imagination to application.
Features
- Standalone front and back-ends
- Can be hosted separately
- No frontend build step
- No runtime npm dependencies
- Reactive
window.state+ custom web components - Plugin-driven backend (
src/plugins) - Auth (auth0, bearer) | Db (jsonl) | llm (OpenAI-compatible providers)
Quick Start
Or clone this repo:
CLI
Manage server with:
npx vanilla-light <command> -- or -- vlserver <command>
Common commands:
vlserver start vlserver config vlserver port 3000 vlserver insecure true vlserver insecure false vlserver certsdir certs vlserver +plugin auth/bearer.js vlserver -plugin auth/bearer.js
Configuration
~/.vanilla-light/config.json
{
"use_plugins": [
"always/logging.js",
"auth/auth0.js",
"auth/bearer.js",
"storage/s3.js",
"storage/kvfile.js",
"llm/openai.js"
],
"port": 3000,
"disable_ssl": true,
"certs_dir": "certs"
}HTTPS should generally be used. Enable SSL, except when behind a reverse-proxy.
Frontend
The front-end consists of a client.js file, which contains helper functions to send/get key vals stored on the server.
Client import:
import { $, $$, get, set, del, me } from '/client.js' -- or -- import { $, $$, get, set, del, me } from 'https://unpkg.com/vanilla-light/client.js'
Web components are defined and used in the simplest way. A components.js file contains their definitions.
All components are exported as components from components.js.
A component is simply a function that returns (or renders html) and runs at page load.
export const components = {
"simple-hello": () => `Hello world!`,
"hello-world": {
prop: (data) => `${data} World`,
render: function(data) {
return this.prop(data);
}
}
};
/client.js automatically imports /components.js when present.
Client server architecture
Server secrets
Plugins get their secrets (api keys, etc..) from env
AUTH0_DOMAIN=... AUTH0_CLIENT_ID=... AUTH0_CLIENT_SECRET=... CLOUDFLARE_ACCESS_KEY_ID=... CLOUDFLARE_SECRET_ACCESS_KEY=... CLOUDFLARE_BUCKET_NAME=... CLOUDFLARE_PUBLIC_URL=... # Any OpenAI-compatible Chat Completions endpoint LLM_ENDPOINT=https://api.openai.com/v1/chat/completions # API key for whatever provider powers LLM_ENDPOINT (OpenAI, OpenRouter, etc.) LLM_API_KEY=...
API Definition
Main routes:
POST /register,
POST /login
GET /me
POST/GET/DELETE /{key}
PUT /{key}
PROPFIND /
PATCH /{key}
POST /llm/chat
Writing a Server Plugin
File: src/plugins/<group>/<name>.js
import { json, redir } from '../../server.js' export default function plugin(app) { app.routes = { ...app.routes, 'GET /hello': async (req) => json({ hello: 'world', user: req.user?.sub || null }), 'GET /go-home': async () => redir('/') } }
Writing Custom Web Components
Define in public/components.js:
export const components = { 'hello-card': (data) => `<div>Hello ${data || 'world'}</div>`, 'user-badge': { render: async () => { const u = await window.me() return `<b>${u?.name || 'guest'}</b>` } } }
Use in HTML:
<hello-card data="name"></hello-card> <user-badge></user-badge> <script>window.state.name = 'Chris'</script>
window.state Reactivity
window.state is a Proxy.
window.state.count = 1
-> proxy set(...)
-> find [data="count"]
-> render matching registered component
Tests
Run with server up:
bash test/server.sh
# optional
BASE=https://localhost:3000 bash test/server.sh