Internals
Server Architecture
Bascik's server infrastructure powers both local development (bascik) and per-request production serving (bascik --serve). Designed as a modular 4-tier pipeline, the server handles request routing, static file serving, data-bascik-server request script execution, live reload SSE streams, in-memory caching, and security hardening.
Modular Architecture (server.ts, http.ts, http2.ts, pki.ts)
Bascik separates protocol management from request routing using a 4-tier architecture:
[transpile.ts (Dev) / serve.ts (Prod)]
│
▼ (startServer)
[server.ts Orchestrator]
│
┌────────┴────────┐
│ (enableTls: │ (enableTls:
│ false) │ true)
▼ ▼
[http.ts] [pki.ts Cert Gen]
(HTTP/1.1 Server) │
│ ▼
│ [http2.ts]
│ (HTTP/2 Server)
│ │
└────────┬────────┘
▼
[server.ts: createRequestHandler]
│
┌────────────────┴────────────────┐
▼ (Dev: mem.ts) ▼ (Prod: dist/)
[In-Memory Store] [Disk Filesystem]
│ │
└────────────────┬────────────────┘
▼
[data-bascik-server Execution]
│
▼
[HTTP Response]server.ts: Central server orchestrator. Defines the unifiedcreateRequestHandler()routing pipeline,startServerInstance()port binder, rate limiter, security header handler, and top-levelstartServer()dispatcher.http.ts: Plaintext HTTP/1.1 server (node:http). Wrapshttp.IncomingMessageandhttp.ServerResponseinto Bascik's request context.http2.ts: Opt-in encrypted HTTP/2 server (node:http2). WrapsServerHttp2Streaminto Bascik's request context.pki.ts: Generates self-signed TLS certificates whenenableTls: trueis configured and certificate files are missing on disk.
Plaintext HTTP/1.1 Default vs HTTP/2 TLS
Plaintext HTTP/1.1 is active by default because it works instantly across all local development tools and integrated browsers without untrusted certificate warnings or platform trust setup.
For production HTTP/2 protocol parity during local development, enable TLS in bascik.config.ts:
export default {
prodServer: {
enableTls: true, // Server boots over https://localhost:8443 (HTTP/2)
},
};When enableTls: true is active, pki.ts looks for local bascik-cert.pem and bascik-privkey.pem files. If missing, it attempts to generate CA-trusted certificates using mkcert (if available) or falls back to OpenSSL self-signed certificates.
Development Server Mode (bascik)
During local development, bascik compiles pages into memory and starts the watch and live-reload systems.
In-memory page store (mem.ts)
The MemoryStore class manages rendered pages during development without writing intermediate files to disk on every edit:
#files: Maps HTTP paths (such as/getting-started) toStoredPageobjects containing raw HTML buffers, pre-compressed Brotli buffers, and component usage lists.#components: Inverted index mapping each component name to theSet<string>of page paths using it. This index enables selective re-transpilation when a single component changes.#openPages: Tracks active SSE live-reload connections. Pages currently open in a browser tab are transpiled first during batch rebuilds so visible tabs refresh immediately.
Brotli compression during development uses minimum quality (BROTLI_MIN_QUALITY = 1) for instant background compression without clogging Node.js C++ threadpool workers.
Boot page during startup (boot-page.ts)
The development server binds its port immediately while page transpilation runs asynchronously. Requests arriving before a page finishes transpiling receive a lightweight boot page displaying a spinner and status message.
The boot page connects to /bascik-live-reload. When the requested page finishes transpiling, the "transpiled" event fires, the SSE connection receives a reload signal, and the browser fetches the actual page automatically. Once watchFiles() completes the initial build, the isBooting flag is cleared and unmatched paths fall through to 404 handling. The boot page is never used in production mode.
Watch system (watch.ts)
Three native filesystem watchers (chokidar) handle source file updates:
- Static assets watcher: Copies non-HTML files in
pages/todist/onaddorchange, deletes them onunlink, and triggers a live-reload event. - Page HTML watcher: Listens for
.htmlfile changes inpages/. Triggers full or single-page transpilation and updatesMemoryStore. - Component watcher: Listens for changes in
components/. On change or deletion, uses the inverted component index (#components) to selectively rebuild only affected pages.
Live reload (live-reload.ts)
Live reload uses Server-Sent Events (SSE) via GET /bascik-live-reload. Bascik injects a lightweight SSE client script into HTML pages in development mode. The script listens for reload messages, auto-reconnects on browser tab focus or visibility changes, and cleanly closes streams on page unload. Production builds strip this script entirely.
Production Server Mode (bascik --serve)
When launched with bascik --serve or BASCIK_PROD_SERVER=1, Bascik runs as a production HTTP server (serve.ts).
Serving from dist/
Production mode skips file watchers, live-reload injection, and in-memory page storage. Instead, it serves static files and HTML directly from the dist/ directory generated by bascik --build.
Per-request data-bascik-server execution (server-scripts.ts)
Pages containing <script data-bascik-server> blocks are executed on every request:
- Request context packaging: Bascik packages request details (
path,method,headers,searchParams) into a JSON object set asprocess.env.BASCIK_REQUEST. - Isolated child process execution: Server scripts run as Node.js ESM modules in isolated child processes with top-level
awaitandimportsupport. - Stdout injection: The script's
stdoutoutput replaces the<script data-bascik-server>tag in the response HTML. - Source remapping: Exceptions and stack traces are remapped back to source HTML filenames and line numbers (
stack-trace.ts).
Caching and performance (cacheHttp)
Production mode enables cacheHttp: true by default:
- ETag support: Generates strong ETag hashes for HTML responses and returns
304 Not Modifiedwhen the client'sif-none-matchheader matches. - Cache-Control headers: Adds
Cache-Control: public, max-age=3600to static assets. - Max-quality Brotli compression: Uses
BROTLI_MAX_QUALITY = 11for optimal bandwidth savings.
Production rate limiting
Production mode enforces a rate limit of 500 requests per 10-second window per IP address by default. Clients exceeding the limit receive 429 Too Many Requests with a Retry-After header. Rate limiting is inactive during development mode, and can be disabled via prodServer: { rateLimit: false } in bascik.config.ts.
Development vs Production Comparison
| Capability | Development (bascik) | Production (bascik --serve) |
|---|---|---|
| Entry Module | transpile.ts | serve.ts |
| Page Storage | MemoryStore in memory (mem.ts) | Pre-built files in dist/ |
| Brotli Quality | BROTLI_MIN_QUALITY = 1 | BROTLI_MAX_QUALITY = 11 |
| HTTP Caching | Disabled (cacheHttp: false) | Enabled (cacheHttp: true with ETags & 304s) |
| Rate Limiting | Disabled | Active (500 req / 10s per IP) |
| Live Reload SSE | Injected & active | Stripped & inactive |
| File Watchers | Active for assets, pages, components | Inactive |
| Boot Page | Active during initial build | Disabled |
data-bascik-server Execution | On-demand per request | Per request |
Shared Security & Reliability
Both development and production server modes share core security and lifecycle mechanisms:
Security response headers
Every response includes standard security headers:
| Header | Value |
|---|---|
x-content-type-options | nosniff |
x-frame-options | SAMEORIGIN |
referrer-policy | strict-origin-when-cross-origin |
permissions-policy | interest-cohort=() |
Path traversal protection
Static asset requests are normalized and validated to ensure the resolved path remains strictly within the dist/ directory. Requests attempting path traversal via /../ receive an immediate 400 Bad Request response before file I/O occurs.
Graceful shutdown
The server registers signal handlers for SIGTERM and SIGINT. Upon receiving a signal, it stops accepting new connections, closes active SSE streams, destroys HTTP/2 sessions, shuts down filesystem watchers, and exits cleanly within a 10-second grace period.
E2E Server Testing
Server behavior is validated through Playwright E2E suites across four environment configurations:
playwright.dev.config.ts: Dev server (bascik) live reload, watchers, and boot page.playwright.server.config.ts: Production server (bascik --serve) over HTTP/1.1.playwright.server-http2.config.ts: Production server over encrypted HTTP/2 (HTTPS).playwright.config.ts: Static build output serving.