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:

text
[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]
  1. server.ts: Central server orchestrator. Defines the unified createRequestHandler() routing pipeline, startServerInstance() port binder, rate limiter, security header handler, and top-level startServer() dispatcher.
  2. http.ts: Plaintext HTTP/1.1 server (node:http). Wraps http.IncomingMessage and http.ServerResponse into Bascik's request context.
  3. http2.ts: Opt-in encrypted HTTP/2 server (node:http2). Wraps ServerHttp2Stream into Bascik's request context.
  4. pki.ts: Generates self-signed TLS certificates when enableTls: true is 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:

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) to StoredPage objects containing raw HTML buffers, pre-compressed Brotli buffers, and component usage lists.
  • #components: Inverted index mapping each component name to the Set<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:

  1. Static assets watcher: Copies non-HTML files in pages/ to dist/ on add or change, deletes them on unlink, and triggers a live-reload event.
  2. Page HTML watcher: Listens for .html file changes in pages/. Triggers full or single-page transpilation and updates MemoryStore.
  3. 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:

  1. Request context packaging: Bascik packages request details (path, method, headers, searchParams) into a JSON object set as process.env.BASCIK_REQUEST.
  2. Isolated child process execution: Server scripts run as Node.js ESM modules in isolated child processes with top-level await and import support.
  3. Stdout injection: The script's stdout output replaces the <script data-bascik-server> tag in the response HTML.
  4. 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 Modified when the client's if-none-match header matches.
  • Cache-Control headers: Adds Cache-Control: public, max-age=3600 to static assets.
  • Max-quality Brotli compression: Uses BROTLI_MAX_QUALITY = 11 for 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

CapabilityDevelopment (bascik)Production (bascik --serve)
Entry Moduletranspile.tsserve.ts
Page StorageMemoryStore in memory (mem.ts)Pre-built files in dist/
Brotli QualityBROTLI_MIN_QUALITY = 1BROTLI_MAX_QUALITY = 11
HTTP CachingDisabled (cacheHttp: false)Enabled (cacheHttp: true with ETags & 304s)
Rate LimitingDisabledActive (500 req / 10s per IP)
Live Reload SSEInjected & activeStripped & inactive
File WatchersActive for assets, pages, componentsInactive
Boot PageActive during initial buildDisabled
data-bascik-server ExecutionOn-demand per requestPer 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:

HeaderValue
x-content-type-optionsnosniff
x-frame-optionsSAMEORIGIN
referrer-policystrict-origin-when-cross-origin
permissions-policyinterest-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.