Fuma studio

Js-schema

Introduction

I don’t enjoy writing raw JSON Schema, and I also didn’t want to pull in a pile of npm packages just to solve a problem I understood well enough myself. Js-schema exists mostly because I like the ergonomics of tools like zod and convex, and I wanted something in that direction opinionated, lightweight, and tailored to how I actually describe APIs.

The result is a small DSL that looks familiar if you’ve used schema builders before, but intentionally cuts corners where strict JSON Schema compliance becomes more painful than useful. You can think of it as JSON Schema with extra steps, except those steps are there to make authoring, reading, and maintaining Rest APIs less annoying.

Some parts are spec-compliant, some are not. That trade-off is deliberate.


Methods

Below are the core schema builders exposed by v. Not all of them map 1:1 to JSON Schema keywords, and that’s fine. The goal here is expressiveness and clarity, not strict adherence to a spec.

TypeDescriptionExampleStatus
stringDefines a string value. Supports additional constraints like enums, defaults, etc.v.string()done
numberDefines a numeric value. Intended for integers and floats unless further constrained.v.number()done
nullExplicitly allows a null value. Useful when modeling nullable fields without unions.v.null()done
objectDefines an object with a fixed set of properties and their schemas.v.object({ key: v.string() })done
oneOfDefines a union type where the value must match exactly one of the provided schemas.v.oneOf([v.string(), v.number()])done
arrayDefines a homogeneous array where all items share the same schema.v.array(v.string())done
array (mixed)Non-homogeneous arrays are also supported when order and variance matter.v.array([v.string(), v.number()])done
response bodyDescribes a response payload, usually keyed by status or outcome.v.reply({ ok: v.json({ ... }) })done
textRaw text payloads (non-JSON). Typically used for plain text responses.v.text("plain text")todo
xmlXML payloads. Mostly useful for legacy or external APIs.v.xml("<xml />")todo
jsonJSON payload shorthand, usually wrapping an object schema.v.json({ key: v.string() })complete
binaryBinary or file-based payloads (uploads/downloads).v.file("example.png")wip

Auth methods

Authentication methods are modeled as first-class exports. Each method return an auth definition that can be inherited by requests in the folder.

// HTTP Basic authentication using a username and password pair.
export const basicAuth = v.basicAuth();
// Bearer token authentication, commonly used with JWT-based APIs.
export const bearerAuth = v.bearerAuth({ format: "JWT" });
// API key passed via a custom HTTP header (for example: x-api-key).
export const apiKeyHeader = v.apiKeyHeaderAuth({ name: "x-api-key" });
// API key passed as a query parameter on the request URL.
export const apiKeyQuery = v.apiKeyQueryAuth({ name: "api-key" });

These are intentionally simple and declarative.


Server

Server definitions describe where your API is hosted. You can define a single server, multiple servers, or even parameterized servers using variables.

export const servers = v.server("https://getbruno.com");

Multiple servers are supported when your API is deployed to more than one region or environment:

export const servers = v.array([
  v.server("https://eu.getbruno.com"),
  v.server("https://us.getbruno.com"),
]);

For more advanced setups, server URLs can be parameterized using variables with constraints and defaults:

export const servers = v
  .server("https://{{location}}.vercel.com")
  .vars({
    location: v.string().enum(["eu", "us"]).default("us"),
  });

This allows the same schema to adapt cleanly across environments without duplicating definitions or hardcoding URLs.

Validation

This is not a validation library and I don't intend to or have any plans to make it one.