# ovr The Streaming Framework ## Introduction Designed to optimize [Time-To-First-Byte](https://web.dev/articles/ttfb#what_is_ttfb), ovr evaluates components in parallel and streams HTML in order by producing an [`AsyncGenerator`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/AsyncGenerator) of HTML that feeds directly into the streamed response. For example, for the following component: ```tsx function Component() { return

hello world

; } ``` ovr generates three chunks of HTML: ```ts "

"; // streamed immediately "hello world"; // next "

"; // last ``` ## Asynchronous streaming While this streaming is trivial for a paragraph, consider when a component is asynchronous: ```tsx async function Username() { const user = await getUser(); // slow... return {user.name}; } function Component() { return (

hello

); } ``` Instead of waiting for `Username` to resolve before sending the entire `Component`, ovr will send what it has immediately and stream the rest as it becomes available. ```ts "

"; "hello "; // streamed immediately // for await (const chunk of Username()) { ... ""; "username"; ""; "

"; ``` ## Render how browsers read Web browsers are [built for streaming](https://developer.mozilla.org/en-US/docs/Web/Performance/Guides/How_browsers_work#parsing), they parse and paint HTML as it arrives. [Most critically, the head](https://web.dev/learn/performance/understanding-the-critical-path#what_resources_are_on_the_critical_rendering_path) of the document can be sent immediately to start the requests for linked assets (JavaScript, CSS, etc.) and start parsing before the HTML has finished streaming. ovr's architecture gives you streaming server-side rendering out of the box. No hydration bundle, no buffering---just HTML delivered _in order_, as soon as it's ready. # Get Started Getting started with ovr ## Installation Install the `ovr` package from npm using your preferred package manager. ```bash npm i ovr ``` Alternatively, you can setup ovr with a pre-configured template using [Vite with domco](https://domco.robino.dev): ```bash npx create-domco@latest --framework=ovr ``` ## JSX configuration To utilize JSX, add the following options to your `tsconfig.json` to enable the JSX transform. TypeScript, Vite, or esbuild will pickup the option from this file. ```json { "compilerOptions": { "jsx": "react-jsx", "jsxImportSource": "ovr" } } ``` > While the ovr package does not depend on React, it uses the same JSX transform as React that is built into `tsc` and other build tools. Or you can use a comment if you are using ovr in conjunction with another framework to specify the import source for a specific module where you are using ovr. ```tsx /** @jsx jsx */ /** @jsxImportSource ovr */ ``` ## Compatibility ovr uses entirely standard JavaScript APIs, so it can run anywhere. The ovr `App` server can be used in any Fetch API compatible runtime via [`App.fetch`](/03-app#fetch). Here are a few ways to create a Fetch based HTTP server in various JavaScript runtimes. - [domco](https://domco.robino.dev) - run `npm create domco` and select `ovr` framework - [Nitro](https://nitro.build/) - [example repo](https://github.com/rossrobino/nitro-ovr) - [Cloudflare Vite Plugin](https://developers.cloudflare.com/workers/vite-plugin/get-started/) + [vite-ssr-components](https://github.com/yusukebe/vite-ssr-components) - [Node + srvx](https://srvx.h3.dev/) - [Bun HTTP server](https://bun.sh/docs/api/http) - [Deno HTTP server](https://docs.deno.com/runtime/fundamentals/http_server/) For example, using `srvx` you can plug `app.fetch` into the `serve` [options](https://srvx.h3.dev/guide/handler). ```tsx // src/index.tsx import { App } from "ovr"; import { serve } from "srvx"; const app = new App(); app.use(() =>

Hello World

); serve({ fetch: (req) => app.fetch(req) }); ``` Then can compile `tsx` into `js` with TypeScript, and run the server with Node. ```bash tsc && node dist/index.js ``` # Render Easily parallelize async operations and render strings with ovr. ## JSX ovr has a built in JSX import source that can be used to parallelize component renders and output HTML. You can also use the [`Render`](#render) class on it's own without using JSX. Components are functions that return a `JSX.Element`. Use them to declaratively describe and reuse parts of your HTML. Define a `props` object as a parameter for a component to pass arguments to it. ```tsx import type { JSX } from "ovr"; function Component(props: { children?: JSX.Element; color: string }) { return
{props.children}
; } ``` Now the component can be used as it's own tag within other components. > Use capital letters for components to distinguish them from HTML (or _intrinsic_) elements. ```tsx function Page() { return (

Hello world

Children
); } ``` Props are passed in like an HTML attribute `name={value}`, while `children` is a special prop that is used to reference the element(s) in between the opening and closing tags. ovr aligns with the standard (all lowercase) HTML attributes---all attributes will be rendered exactly as they are written. > If you're coming from React, this means you must use `class` and `for` instead of `className` and `htmlFor` respectively. There is also no need to provide a `key` attribute in when rendering lists. ## Async components Components can be asynchronous, for example you can `fetch` directly in a component. ```tsx async function Data() { const res = await fetch("..."); const data = await res.text(); return
{data}
; } ``` ## Generator components Components can also be generator functions for more fine grained control and [memory optimization](/demo/memory). When utilizing generators `yield` values instead of returning them. ```tsx async function* Generator() { yield

start

; // streamed immediately await promise; // async work yield

after

; } ``` ## Parallelization These three components await in [parallel](/demo/parallel) when this component is evaluated. Then they will stream in order as soon as they are ready. ```tsx function Page() { return (
); } ``` > The order of your components does not affect when they are evaluated, but it does impact when they will display. If `Username` is the slowest component, `Generator` and `Data` will be queued but only streamed after `Username` completes. This ensures no client-side JavaScript has to run for users to see your content. ## Data types You can `return` or `yield` most data types from a component, they will be rendered as you might expect. Each of these data types can also be used to create a new [`Render`](#render). ```tsx function* DataTypes() { yield null; // "" yield undefined; // "" yield false; // "" yield true; // "" yield "string"; // "string" yield 0; // "0"; yield

jsx

; // "

jsx

" yield ["any-", "iterable", 1, null]; // "any-iterable1" yield () => "function"; // "function" yield async () => "async"; // "async" } ``` > Check out the [source code](https://github.com/ovrjs/ovr/blob/main/packages/ovr/src/render/index.ts) for the `render` function to understand how ovr evaluates each data type. ## Render To render any element on it's own (for example, if you aren't using returning them from `Middleware` or need to call them separately), use the `Render` class. ### Async iterable Create a new `Render` async iterable that yields escaped `Chunk`s of HTML. ```tsx import { Render } from "ovr"; function Component() { return

element

; } const render = new Render(); for await (const chunk of render) { console.log(chunk.toString()); // HTML string } ``` > Any of the supported [data types](#data-types) can also be rendered directly without JSX. For example, the following array can be passed into `Render` running all async operations in parallel. ```ts const render = new Render([ async () => "hello", "world", [ async function* () { yield "!"; }, ], ]); for await (const chunk of render) { console.log(chunk.toString()); } ``` Set `Render.Options.safe` to `true` to bypass HTML escaping to render [other types of content](/05-middleware#return-value). ```ts new Render(element, { safe: true }); ``` ### Stream Turn an element into a `ReadableStream` using `Render.stream`. This pipes a `Render` into a `ReadableStream`. This stream is optimized for generating HTML---it ensures backpressure is properly handled for slower clients and generation stops if the client aborts the request. ```tsx import { Render } from "ovr"; function Component() { return

element

; } const stream = Render.stream(); const response = new Response(stream, { "content-type": "text/html; charset=utf-8", }); ``` ### String Convert any element into a `string` of HTML with `Render.string`. This iterates through the `Render` and joins the results into a single string. ```tsx import { Render } from "ovr"; function Component() { return

element

; } const str = await Render.string(); ``` ### Raw HTML To render safe HTML **without escaping**, use the `Render.html` method. ```tsx import { Render } from "ovr"; const html = "

Safe to render

"; function Component() { return
{Render.html(html)}
; } ``` # App Creating an application with ovr. ## Create To create a web server with ovr, initialize a new `App` instance: ```ts import { App } from "ovr"; const app = new App(); ``` The `App` API is inspired by and works similar to frameworks such as [Express](http://expressjs.com), [Koa](https://koajs.com), and [Hono](https://hono.dev/). ## Options The following values can be configured when creating the `App`. ### Trailing Slash ovr handles [trailing slash](https://bjornlu.com/blog/trailing-slash-for-frameworks) redirects automatically, you can customize the redirect preference. ```ts new App({ trailingSlash: "always" }); ``` ### CSRF ovr comes with basic [cross-site request forgery](https://developer.mozilla.org/en-US/docs/Web/Security/Attacks/CSRF) protection. ```ts new App({ csrf: false }); // disables the built-in protection ``` ### Form [Multipart form data options](/06-multipart#options). ```ts new App({ form: { memory: 12 * 1024 * 1024, // increase to 12MB payload: 1024 ** 3, // increase to 1GB parts: 4, // only accept up to 4 parts }, }); ``` ## Use _Use_ the `use` method to register [routes](/04-route) and [middleware](/05-middleware) to your application. ```tsx app.use(page); // single app.use(page, login, mw); // multiple app.use({ page, login, mw }); // object app.use([page, login, mw]); // array // any combination of these also works ``` This makes it easy to create a module of routes and/or middleware, ```tsx // home.tsx import { Route } from "ovr"; export const page = Route.get("/", (c) => { // ... }); export const login = Route.post((c) => { // ... }); ``` and then use them all at once: ```tsx // app.tsx import * as home from "./home"; app.use(home); // uses all exports ``` ## Fetch Use the `fetch` method to create a `Response`, this is the `Request` handler for your application. ```ts const response = await app.fetch("https://example.com"); ``` ## Head requests ovr automatically handles [`HEAD`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods/HEAD) requests, each will be routed to the corresponding `GET` route. Middleware will execute but `Context.res.body` will cleaned up and set to `null` before building the final response. # Route ovr application routes. ## Create ovr's built-in router offers efficient matching, supporting static paths, [parameters](#parameters), and [wildcards](#wildcard). Utilizing a [radix trie](https://en.wikipedia.org/wiki/Radix_tree) structure ensures performance does not degrade as more routes are added. Create a route to a specific resource in your application with the `Route` class. Construct the route with an HTTP `method`, the route `pattern`, and the [`middleware`](/05-middleware) to handle the request. ```ts import { Route } from "ovr"; const route = new Route("GET", "/", () => "html"); ``` ## Parameters Create a parameter for a route by prefixing a segment with a colon `:`. The pattern `/api/:id` sets `Context.params.id` to the actual path segment requested, for example `/api/123`. The name of the parameter is extracted from the pattern using TypeScript to ensure `Context.params` always has the correct type. ```ts new Route("GET", "/api/:id", (c) => { // matches "/api/123" c.params.id; // "123" }); ``` ## Wildcard Use an asterisk `*` to match all remaining segments in the route. ```ts new Route("GET", "/files/*", (c) => { c.params["*"]; // matched wildcard path (ex: "images/logo.png") }); ``` ## Prioritization Routes are prioritized in this order: **Static** > **Parametric** > **Wildcard** Given three routes are added in any order: ```ts app.use(new Route("GET", "/hello/*")); app.use(new Route("GET", "/hello/world")); app.use(new Route("GET", "/hello/:name")); ``` More specific matches are prioritized. The following pathnames would match the corresponding patterns: | Pathname | Pattern | | ------------------- | -------------- | | `/hello/world` | `/hello/world` | | `/hello/john` | `/hello/:name` | | `/hello/john/smith` | `/hello/*` | ## Get `Route.get` makes it easy to create a [GET](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods/GET) route and corresponding `Anchor`, `Button`, and `Form` components for it. This ensures if you change the route's pattern, you don't need to update all of the links to it throughout your application. Anytime you need to generate a link to a route use the `Anchor` component. ```tsx const page = Route.get("/", () => { return (
{/* */} Home {/*
); }); ``` ## Post There is also a `Route.post` function that will create a [POST](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Methods/POST) route and corresponding `Form` and `Button` elements. Anytime you need to handle a form submission, use the generated `Form` component. These components also automatically set the `enctype=multipart/form-data` attribute. For `Route.post`, ovr will automatically generate a unique pathname for the route based on a hash of the middleware provided. ```tsx const login = Route.post(async (c) => { const data = await c.req.formData(); // ... c.redirect("/", 303); }); const page = Route.get("/", () => { return (
{/* */} ... {/*
); }); ``` You can set the pattern manually if you need a stable pattern or if you are using parameters. ```tsx const custom = Route.post("/custom/:pattern", (c) => { // ... }); ``` ## Props Components created via helpers have the following props available: - `params` - if the route's pattern has parameters, they must be passed as a prop to properly construct the URL. - `search` - [URLSearchParams](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams/URLSearchParams) to append to the URL, passed into `URLSearchParams` constructor to create the query string. - `hash` - [fragment hash](https://developer.mozilla.org/en-US/docs/Web/API/URL/hash) appended with a `#` at the end of the URL. ```tsx const page = Route.get("/hello/:name", () => { return ( // ... ); }); ``` ## Properties Given the following `Route`, a variety of other properties are available to use. ```tsx const page = Route.get("/hello/:name", (c) =>

Hello {c.params.name}

); ``` ### Method The route's HTTP method. ```ts page.method; // "GET" ``` ### Middleware All `Middleware` added to the route. ```ts page.middleware; // Middleware[] ``` ### Params `Params` is a type helper to get the specific params of the route based on the pattern. ```ts typeof page.Params; // { name: string } ``` ### Pattern The pattern the route was created with. ```ts page.pattern; // "/hello/:name" ``` ### Pathname The `pathname` method inserts params into the pattern. It provides type safety to ensure you always pass the correct params (or no params) to create the pathname. In this case, given the pattern `/hello/:name`, the `name` property must be passed in on the `params` object. ```ts page.pathname({ name: "world" }); // `/hello/${string}` page.pathname({ name: "world" } as const); // "/hello/world" ``` Using incorrect params results in a type error: ```ts page.pathname({ id: "world" }); // Error: 'id' does not exist in type '{ name: string; }' ``` > You can create a list of exact pathnames for given params: ```ts const params = [ { name: "world" }, { name: "ross" }, ] as const satisfies Route.Params[]; const pathnames = params.map((p) => page.pathname(p)); // ("/hello/world" | "/hello/ross")[] ``` ### Relative URL The `url` method creates a _relative_ URL (without the `origin`) for the route. This method is similar to `pathname`, but also provides the ability to also pass [`search`](https://developer.mozilla.org/en-US/docs/Web/API/URLSearchParams) and [`hash`](https://developer.mozilla.org/en-US/docs/Web/API/URL/hash) options to create the URL. ```ts // /hello/world?search=param#hash const relativeUrl = page.url({ params: { name: "world" }, search: { search: "param" }, hash: "hash", }); const absoluteUrl = new URL(relativeUrl, "https://example.com"); absoluteUrl.href; // https://example.com/hello/world?search=param#hash ``` # Middleware Understand how ovr handles each request. ## Create Middleware are functions that are composed together to handle requests. Most middleware will be created inline as an argument when creating a route. Middleware can also be created on it's own to be used globally or to apply to routes. ```ts import type { Middleware } from "ovr"; const mw: Middleware = async (c, next) => { c; // Middleware.Context - request context await next(); // Middleware.Next - dispatch the next middleware in the stack }; ``` > Middleware are just functions, use the `Middleware` type helper to easily type the parameters if you are creating them outside of a route. ## Composition There are two levels of middleware in an ovr application: 1. **Global** - any middleware passed directly to `App.use` 2. **Route** - middleware added to a specific `Route` For each request, ovr creates an array containing the **global** middleware, then the **matched route**'s middleware (if there is a match). The first middleware in the array will be called, then you can dispatch the `next` middleware within the first by calling `await next()` or returning `next()`. ```ts 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` }, // ... ); // 1 // 2 // 3 ``` ## Context ovr creates `Context` _(in these docs it's abbreviated as `c`)_ with the current request, then passes it as the first argument into each middleware function to be read and modified. ## Request `Context.req` contains information about the current request such as the [`url`](https://developer.mozilla.org/en-US/docs/Web/API/URL) or [`params`](/06-routing#parameters). ```ts Route.get("/api/:id", (c) => { c.req; // original `Request` c.url; // parsed web `URL` c.params; // type-safe route parameters { id: "123" } c.route; // matched `Route` c.cookie.get(name); // get cookie c.form(); // multipart form data }); ``` ## Response `Context.res` stores the arguments that will be passed into `new Response()` after middleware has executed. These properties can be set directly: ```ts Route.get("/api/:id", (c) => { c.res.body = "# Markdown"; // BodyInit | null | undefined c.res.status = 200; // number | undefined c.res.headers.set("content-type", "text/markdown; charset=utf-8"); // Headers }); // internally, ovr creates the Response with final values: new Response(c.res.body, { status: c.res.status, headers: c.res.headers }); ``` The response can be also be set with helper functions or by returning a value from the middleware. ```tsx Route.get("/api/:id", (c) => { // use helper functions to set common headers c.html(body, status); // HTML c.text(body, status); // plain text c.json(data, status); // JSON c.redirect(location, status); // redirect c.cookie.set(name, value, options); // set cookie if (c.etag(str)) return; // ETag - sets 304 if match // return anything from middleware (see next section) return

stream

; }); ``` ### Return value ovr handles the return value from middleware in two ways. 1. **Response** - If a `Response` is returned from middleware, `Context.res.body`, `Context.res.status` will be set to the response's values, and its `headers` will be merged into `Context.res.headers`. ```ts app.use(() => new Response("Hello world")); ``` 2. **Stream** Any value that is returned from middleware will be passed into [`Render.stream`](/02-render#stream) and assigned to `Context.res.body`. Returning a value sets the content type header to HTML unless it has already been set. HTML will be escaped during the render while other content types will not. This makes it easy to stream other types of content than HTML. For example, to create a [server sent event](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events) stream, simply set the content type header and return a generator function: ```ts // simulate latency const delay = () => new Promise((r) => setTimeout(r, 500)); Route.get("/api/:id", (c) => { // set the content type header to create a SSE c.res.headers.set("content-type", "text/event-stream"); // passed into `render.stream` return async function* () { yield "data: server\n\n"; await delay(); yield "data: sent\n\n"; await delay(); yield "data: event\n\n"; }; }); ``` > ovr JSX can be used to create streams of other types of content too without using the intrinsic HTML elements. # Multipart How to handle user data with the built-in multipart form data parser. ## Form data When a user submits an [HTML form](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/form#enctype) with the `enctype="multipart/form-data"` attribute or creates a [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/RequestInit#body) with form data, a multipart request is created that streams the information from the client to the server. The platform built-in [`Request.formData`](https://developer.mozilla.org/en-US/docs/Web/API/Request/formData) buffers the entire request body in memory which limits scalability for very large files or requests. ovr's `Multipart` parser addresses this by streaming the request body in [chunks](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Transfer-Encoding#chunked), detecting boundaries incrementally without full buffering. - **Streaming Processing** - Holds one chunk in memory at a time for boundary detection. Stream massive files directly to disk, S3, or proxies without buffering. - **Compatible** - Native Fetch API implementation means zero adapters for modern stacks (Deno, Bun, Hono, H3, SvelteKit, etc.). Each `Multipart.Part` is an instance of `Request`. - **Size Limits** - Configurable `memory` and `payload` thresholds to prevent oversized requests. ## Usage To stream parts of a multipart request, construct a new `Multipart` async iterable, and use a [`for await...of`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for-await...of) loop to iterate through each part. Each `Multipart.Part` extends the web [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) object, so all of the methods such as `part.text()` and `part.bytes()` are available to use. Additional properties provide quick access to form-specific details like the input `name`, `filename`, and content `type`. ```ts import { upload } from "./upload"; import { Multipart } from "ovr"; async function fetch(req: Request) { for await (const part of new Multipart(req)) { part; // extends Request part.headers; // Headers part.body; // ReadableStream part.name; // form input name part.filename; // filename if available part.type; // media type if (part.name === "name") { // buffer a text input const name = await part.text(); } else if (part.name === "photo") { // stream an upload await upload(part.body); } else if (part.name === "doc") { // buffer bytes const bytes = await part.bytes(); } } } ``` > If you are using the parser within ovr app [middleware](<[/05-middleware](/05-middleware#request)>), `Context.form` creates the multipart async iterable with the current request. ```ts import { Route } from "ovr"; const post = Route.post(async (c) => { for await (const part of c.form()) { // ... } }); ``` ### Data If you do need to buffer all the data in memory, the `Multipart.data` method is available as a drop in replacement for [`Request.formData`](https://developer.mozilla.org/en-US/docs/Web/API/Request/formData), enhanced with thresholds. ```ts const mp = new Multipart(req, options); const data = await mp.data(); // FormData ``` ## Options Options are available for the maximum `memory` allocation, max total `payload` size, and max number of `parts`, to prevent attackers from sending massive requests. ```ts const options: Multipart.Options = { memory: 12 * 1024 * 1024, // increase to 12MB payload: 1024 ** 3, // increase to 1GB parts: 4, // only accept up to 4 parts }; // standalone new Multipart(request, options); // set options for the entire app new App({ form: options }); // Context.form sets the options for the current request c.form(options); ``` ## Comparisons ### Platform [`Request.formData`](https://developer.mozilla.org/en-US/docs/Web/API/Request/formData) is a built-in method to parse form data from any request, it **buffers all parts memory** when called. ovr's parser supports streaming and has memory and size guards to prevent abuse. ### Remix [`@remix-run/multipart-parser`](https://github.com/remix-run/remix/tree/main/packages/multipart-parser) is a great option for multipart processing. Its [search function](https://github.com/remix-run/remix/blob/main/packages/multipart-parser/src/lib/buffer-search.ts) (Boyer-Moore-Horspool) has been adapted for use in ovr. It also depends on [`@remix-run/headers`](https://github.com/remix-run/remix/tree/main/packages/headers) which provides a rich API for accessing additional information about each part if needed. Remix incrementally **buffers each _part_ in memory** compared to ovr's incremental processing of each _chunk_. This makes Remix [unable to stream extremely large files](https://github.com/remix-run/remix/pull/10764) if your server cannot hold them in memory, it requires them to be fully buffered before use. ### SvelteKit [SvelteKit's multipart parser](https://github.com/sveltejs/kit/pull/14775) is a full-stack solution to progressively enhance multipart submissions. It uses a [custom encoding](https://bsky.app/profile/rich-harris.dev/post/3m65ghxt4r22t) to stream files when client-side JavaScript is available. If you are using SvelteKit, it makes sense to use this parser, but it is **limited to using within SvelteKit applications with client-side JS**. ### Busboy [`busboy`](https://github.com/mscdex/busboy) is the gold standard solution for multipart parsing for Node. The primary difference from ovr is that busboy is **built for Node** and parses an `IncomingMessage` instead of a Fetch API `Request`. Most modern frameworks are being built around the Fetch API, ovr is compatible with these frameworks without any extra conversion. ## Examples Other examples using the parser for file writes and within other frameworks. ### Node Use ovr app on a Node server to stream a file to disk. ```ts import { createWriteStream } from "node:fs"; import { Writable } from "node:stream"; import { Route } from "ovr"; const upload = Route.post(async (c) => { try { for await (const part of c.form()) { if (part.name === "photo") { await part.body.pipeTo( Writable.toWeb(createWriteStream(`/uploads/${part.filename}`)), ); } } c.text("Upload Complete", 201); } catch (error) { console.error(error); c.text("Upload Failed", 500); } }); ``` ### Deno Pass the `Part.body` directly to `Deno.writeFile`. ```ts import { Route } from "ovr"; const upload = Route.post(async (c) => { try { for await (const part of c.form()) { if (part.name === "photo") { await Deno.writeFile(`/uploads/${part.filename}`, part.body); } } c.text("Upload Complete", 201); } catch (error) { console.error(error); c.text("Upload Failed", 500); } }); ``` ### H3 ```ts import { H3 } from "h3"; import { Multipart } from "ovr"; const app = new H3(); app.post("/upload", async (event) => { for await (const part of new Multipart(event.req)) { // ... } }); ``` ### Hono ```ts import { Hono } from "hono"; import { Multipart } from "ovr"; const app = new Hono(); app.post("/upload", async (c) => { for await (const part of new Multipart(c.req.raw)) { // ... } }); ``` # Chat Learn how to build a basic chat interface with ovr. Here's a chat example with the [OpenAI Responses API](https://platform.openai.com/docs/api-reference/responses). The response is streamed _without_ client-side JavaScript using the async generator `Poet` component. ```tsx import { OpenAI } from "openai"; const client = new OpenAI(); async function* Poet(props: { message: string }) { const response = client.responses.stream({ input: props.message, instructions: "You turn messages into poems.", model: "gpt-5-nano", reasoning: { effort: "minimal" }, text: { verbosity: "low" }, }); for await (const event of response) { if (event.type === "response.output_text.delta") yield event.delta; } } ``` # Form Create a page and POST request handler with ovr. This is a page and post route created with the [`Route.get` and `Route.post` methods](/04-route#get). The generated `` can be used directly within the page's markup. ```tsx import { Route } from "ovr"; export const form = Route.get("/demo/form", (c) => { return (
); }); export const post = Route.post(async (c) => { for await (const part of c.form()) { if (part.name === "name") { console.log(part); } } c.redirect("/", 303); }); ``` # Memory Optimization Demo of using sync generators to optimize memory usage. Using generators can reduce memory consumption which can be useful if you are rendering a large HTML page. Instead of creating the entire component in memory by mapping through a large array, you can use a generator to `yield` elements as you iterate through the array. This allows the server to send the result as it iterates through the generator, users also see the start of the content faster. ```tsx function* OverNineThousand() { for (let i = 0; i < 9_114; i++) yield
; } ``` --- # Parallelization Understand how ovr processes async components in parallel. With ovr, every component is streamed independently allowing you to read this content immediately instead of waiting for the last component to render. The delay does not waterfall since components are generated in parallel with [`Promise.race`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/race). ```tsx async function Delay({ ms }: { ms: number }) { await new Promise((res) => setTimeout(res, ms)); return
{ms}ms
; } function Delays() { const delays = Array.from({ length: 6 }, (_, i) => i * 100); return delays.map((ms) => ); } ``` --- # Todo A basic todo app built with ovr. A server driven todo app that stores data in the URL. ```tsx import { type Middleware, Route } from "ovr"; import * as z from "zod"; export const add = Route.post(async (c) => { const todos = getTodos(c); const { text } = await data(c); todos.push({ id: (todos.at(-1)?.id ?? 0) + 1, text, done: false }); redirect(c, todos); }); export const toggle = Route.post(async (c) => { const todos = getTodos(c); const { id } = await data(c); const current = todos.find((t) => t.id === id); if (current) current.done = !current.done; redirect(c, todos); }); export const remove = Route.post(async (c) => { const todos = getTodos(c); const { id } = await data(c); redirect( c, todos.filter((t) => t.id !== id), ); }); export const todo = Route.get("/demo/todo", (c) => { return ( <>
    {getTodos(c).map((t) => (
  • {t.text}
    x
  • ))}
Reset ); }); const TodoSchema = z.object({ done: z.boolean().optional(), id: z.coerce.number(), text: z.coerce.string(), }); const redirect = ( c: Middleware.Context, todos: z.infer<(typeof TodoSchema)[]>, ) => { const location = new URL(todo.pathname(), c.url); location.searchParams.set("todos", JSON.stringify(todos)); c.redirect(location, 303); }; const getTodos = (c: Middleware.Context) => { const todos = c.url.searchParams.get("todos"); if (!todos) return [{ done: false, id: 0, text: "Build a todo app" }]; return z.array(TodoSchema).parse(JSON.parse(todos)); }; const data = async (c: Middleware.Context) => { const data = await c.form().data(); return TodoSchema.parse({ id: data.get("id"), text: data.get("text") }); }; ```