ovr Documentation

website·Indexed 31 days ago

https://ovrjs.com

A web server framework featuring a built-in router with static paths and parameters, a multipart form data parser, and a Schema API for type-safe validation modeled after Zod and Standard Schema. It includes support for JSX rendering, async components, and middleware composition.

Tokens
6.8K
Snippets
43
Records
46
Agent score
54%

What's inside ovr

  1. Deploy the ovr App server in Fetch-compatible runtimes

    The ovr App server is compatible with any runtime that supports the Fetch API via the App.fetch method. It can be deployed using runtimes such as domco, Nitro, Cloudflare Workers, Node (via srvx), Bun, or Deno.
    // Example using srvx in Node.js
    import { App } from "ovr";
    import { serve } from "srvx";
    
    const app = new App();
    
    app.use(() => <h1>Hello World</h1>);
    
    serve({ fetch: (req) => app.fetch(req) });
    # Compile and run
    tsc && node dist/index.js
  2. Stream multipart form data using Multipart

    The Multipart parser allows for streaming processing of multipart requests (e.g., HTML forms with enctype="multipart/form-data"). Unlike the native Request.formData(), which buffers the entire body in memory, Multipart processes the request in chunks, making it suitable for massive files. Each Multipart.Part extends the web Request object, providing access to standard methods like .text() and .bytes(), as well as form-specific properties like name, filename, and type.
    import { Multipart } from "ovr";
    
    async function fetch(req: Request) {
    	for await (const part of new Multipart(req)) {
    		console.log(part.name);     // form input name attribute
    		console.log(part.filename); // filename if available
    		console.log(part.type);     // media type
    
    		if (part.name === "name") {
    			const name = await part.text();
    		} else if (part.name === "photo") {
    			// Stream the body directly to a destination
    			await upload(part.body);
    		} else if (part.name === "doc") {
    			const bytes = await part.bytes();
    		}
    	}
    }
  3. Stream file uploads in forms

    To stream uploads, mark a file field with .stream(). When calling Context.data(), ovr validates non-streamed fields first. The remaining multipart parts are then exposed on result.stream. Streamed fields should be placed last in the document to ensure previous fields are parsed first.
    import { upload } from "./upload";
    import { Field, Route } from "ovr";
    
    const submit = Route.post(
    	{
    		date: Field.date(),
    		license: Field.file().stream(),
    	},
    	async (c) => {
    		const result = await c.data();
    
    		if (result.issues) return c.redirect(result.url, 303);
    
    		const { date } = result.data;
    
    		if (result.stream) {
    			for await (const part of result.stream) {
    				if (part.name === "license") {
    					await upload(part.body, date);
    				}
    			}
    		}
    
    		c.redirect("/files", 303);
    	},
    );
    
  4. Initialize a web server with the App class

    To create a web server using ovr, initialize a new instance of the App class. The API is designed to be similar to frameworks like Express, Koa, and Hono.
    import { App } from "ovr";
    
    const app = new App();
  5. Handle multipart forms in ovr middleware

    When using the parser within an ovr app middleware, you can use Context.form() to create the multipart async iterable for the current request.
    import { Route } from "ovr";
    
    const post = Route.post(async (c) => {
    	for await (const part of c.form()) {
    		// process parts
    	}
    });
  6. Manage background operations with request.waitUntil

    Use request.waitUntil() to notify the runtime of an ongoing asynchronous operation (such as telemetry or logging) that must complete before the runtime closes the connection, allowing the response to be sent to the client immediately without waiting for the background task.
    import { serve } from "srvx";
    
    async function logRequest(request) {
      await fetch("https://telemetry.example.com", {
        method: "POST",
        body: JSON.stringify({ method: request.method, url: request.url, ip: request.ip }),
      });
    }
    
    serve({
      fetch: (request) => {
        request.waitUntil(logRequest(request));
        return new Response("OK");
      },
    });
  7. Compose global and route-level middleware

    ovr supports two levels of middleware:

    1. Global: Passed via App.use(), these run for every request.
    2. Route: Added to a specific Route object.

    For each request, ovr executes an array consisting of global middleware followed by the matched route's middleware. You can trigger the next middleware in the sequence by using await next() or return next().

    app.use(
    	async (c, next) => {
    		console.log("1");
    		await next(); // dispatches the next middleware below
    		console.log("3");
    	},
    	(c, next) => {
    		console.log("2");
    		return next(); // or `return` instead of `await`
    	},
    );
    
    // Execution order: 1 -> 2 -> 3
  8. Register routes and middleware using app.use

    Use the use method to register routes and middleware. It supports various input formats: single items, multiple arguments, objects, or arrays. You can also import a module of routes and pass the entire module to app.use() to register all exported routes at once.
    // Registering different formats
    app.use(page);
    app.use(page, login, mw);
    app.use({ page, login, mw });
    app.use([page, login, mw]);
    
    // Registering a module of routes
    import * as home from "./home";
    app.use(home);
  9. Authenticate requests to the OpenAI API

    The OpenAI API uses HTTP Bearer authentication. You can provide credentials using either a standard API key or a short-lived access token created via workload identity federation. API keys should be treated as secrets and loaded from environment variables or a key management service rather than hardcoded in client-side code.

    If you belong to multiple organizations or use legacy user API keys, you must specify the target organization and project using the OpenAI-Organization and OpenAI-Project headers.

    # Basic authentication
    Authorization: Bearer OPENAI_API_KEY_OR_ACCESS_TOKEN
    
    # Request specifying organization and project
    curl https://api.openai.com/v1/models \
      -H "Authorization: Bearer $OPENAI_API_KEY" \
      -H "OpenAI-Organization: $ORGANIZATION_ID" \
      -H "OpenAI-Project: $PROJECT_ID"
  10. Trace requests with X-Client-Request-Id

    You can supply a custom unique identifier for each request using the X-Client-Request-Id header. This is particularly useful for tracking requests during timeouts or network issues where the server-generated x-request-id is not received.

    Constraints:

    • Must contain only ASCII characters.
    • Maximum length of 512 characters.
    • Must be unique per request.
    • Failure to meet these constraints results in a 400 error.
    curl https://api.openai.com/v1/chat/completions \
      -H "Authorization: Bearer $OPENAI_API_KEY" \
      -H "X-Client-Request-Id: 123e4567-e89b-12d3-a456-426614174000"
  11. Create reusable UI components with ovr JSX

    ovr provides a built-in JSX import source to declaratively describe HTML. Components are functions that return a JSX.Element. Use capital letters for component names to distinguish them from standard HTML elements. Props are passed as an object parameter, and children is a special prop for nested elements. Unlike React, ovr uses standard HTML attributes (e.g., use class instead of className and for instead of htmlFor) and does not require key attributes for lists.
    import type { JSX } from "ovr";
    
    function Component(props: { children?: JSX.Element; color: string }) {
    	return <div style={`color: ${props.color}`}>{props.children}</div>;
    }
    
    function Page() {
    	return (
    		<div>
    			<h1>Hello world</h1>
    			<Component color="blue">Children</Component>
    		</div>
    	);
    }
  12. Set up a standalone Cloudflare Worker with the Vite plugin

    To create a standalone Cloudflare Worker using the Vite plugin, follow these steps:

    1. Initialize a package.json with "type": "module" to enable ES modules.
    2. Install the required dependencies: vite, @cloudflare/vite-plugin, and wrangler.
    3. Create a vite.config.ts file and include the cloudflare() plugin.
    4. Create a Worker configuration file (wrangler.jsonc or wrangler.toml) specifying the Worker's name and the entry point (main).
    5. Create the Worker entry file (e.g., src/index.ts) exporting a default object with a fetch handler.

    Once configured, you can use the following scripts:

    • dev: Starts the Vite development server.
    • build: Builds the application.
    • preview: Builds and then previews the application.
    • deploy: Builds and deploys the Worker to Cloudflare.
    # Install dependencies
    npm i -D vite @cloudflare/vite-plugin wrangler
    // package.json
    {
      "name": "cloudflare-vite-get-started",
      "private": true,
      "version": "0.0.0",
      "type": "module",
      "scripts": {
        "dev": "vite dev",
        "build": "vite build",
        "preview": "npm run build && vite preview",
        "deploy": "npm run build && wrangler deploy"
      }
    }
    // vite.config.ts
    import { defineConfig } from "vite";
    import { cloudflare } from "@cloudflare/vite-plugin";
    
    export default defineConfig({
      plugins: [cloudflare()],
    });
    // wrangler.jsonc
    {
      "$schema": "./node_modules/wrangler/config-schema.json",
      "name": "cloudflare-vite-get-started",
      "compatibility_date": "2026-09-29",
      "main": "./src/index.ts"
    }
    // src/index.ts
    export default {
      fetch() {
        return new Response(`Running in ${navigator.userAgent}!`);
      },
    };