Skip to content

Introduction

orva is a TypeScript web framework built around the Fetch API. It keeps the handling model small while filling in the layers modern backend services usually need:

  • composable routing and middleware
  • app.use()-level type accumulation
  • validator and zod validator support
  • typed RPC clients generated from the route registry
  • OpenAPI documents with reusable component metadata
  • adapters for Node, Bun, Deno, Cloudflare, Vercel, Netlify, Azure, and AWS Lambda

What to learn on this page

By the end of this page, you should understand:

  • what kind of framework orva is
  • what problems it is meant to solve
  • why the root entry and subpath exports are intentionally separated
  • why validator, RPC, and OpenAPI are treated as one contract system

Design goals

orva is trying to do three things well:

  1. Keep request handling easy to read.
  2. Keep types, docs, clients, and runtime validation close to the same route contract.
  3. Keep export boundaries explicit enough for packaging, reuse, and ecosystem publishing.

That means it is neither a toy framework focused only on a tiny API surface, nor a heavy framework tied to a large all-in-one convention model.

Good fits

  • API services
  • BFF / Backend for Frontend
  • serverless and edge services
  • internal systems that need OpenAPI or typed clients
  • teams that want contract tooling without a heavy framework model

Poor fits

  • a single-file demo that will never use middleware, validation, RPC, or docs
  • a full-stack page rendering framework with batteries included
  • teams already fully locked into another strongly opinionated platform with no reuse plan

Core design

1. The root entry exports only the core

ts
import { createOrva, defineMiddleware } from 'orvajs';

Non-core features come from subpaths:

ts
import { createRPC } from 'orvajs/rpc';
import { serveNode } from 'orvajs/adapters/node';
import { cors } from 'orvajs/middlewares/cors';
import { validator } from 'orvajs/validator';
import { zodValidator } from 'orvajs/validator/zod';
import { createOpenAPIDocument } from 'orvajs/openapi';

This has two direct benefits:

  • it keeps the root entry from becoming bloated
  • it makes both app code and ecosystem code easier to import selectively

2. Middleware types are part of the contract

orva lets defineMiddleware() and validators accumulate types into downstream routes:

ts
import { createOrva, defineMiddleware } from 'orvajs';

const session = defineMiddleware<{ session: { userId: string; role: string } }>(async (c, next) => {
  c.set('session', { userId: 'u_1', role: 'admin' });
  await next();
});

const app = createOrva()
  .use(session)
  .get('/me', (c) => c.json({
    userId: c.get('session')?.userId,
    role: c.get('session')?.role,
  }));

3. Contracts should be reused, not rewritten

One route definition can feed:

  • runtime validation
  • type inference
  • RPC clients
  • OpenAPI generation

That is one of the main differences between orva and lighter routers that stop at request dispatch.

How it differs from common alternatives

DimensionorvaTraditional Express style
Request modelFetch API styleNode / Express-owned request objects
Type flowCan stay continuous from middleware and validator into RPC / OpenAPIUsually needs extra tooling layers stitched together
Export strategyLean root entry, granular submodulesOften uses broad aggregate exports
Multi-runtimeNode-first plus multiple platform adaptersPrimarily Node-centric, with more external bridging
  • New projects: start with Quickstart and ship a minimal service first.
  • Existing API services: add validator, error handling, and middleware first, then layer in RPC / OpenAPI.
  • Platform tooling: prefer granular submodule paths such as orvajs/middlewares/cors.
  1. Quickstart
  2. Context and Responses
  3. Type Flow

Built with VitePress. Structured for production docs, multilingual delivery and long-term versioning.