DocTreen

Schema builder

Lightweight `s` builder when you don't want a runtime schema library.

DocTreen ships a lightweight schema builder for defining typed request and response shapes without a runtime schema library. It exists in all three languages with the same vocabulary — s in Node and Python, S:: in PHP:

const { s } = require('doctreen');
// import { s } from 'doctreen';

s.string()            // { type: 'string' }
s.number()            // { type: 'number' }
s.boolean()           // { type: 'boolean' }
s.null()              // { type: 'null' }
s.unknown()           // { type: 'unknown' }

s.object({
  id:   s.number(),
  name: s.string(),
  bio:  s.optional(s.string()),   // optional field — shown with ? in the UI
})

s.array(s.string())
s.array(s.object({ id: s.number(), tag: s.string() }))

s.optional(s.string())  // wraps any node as optional
s.enum(['admin', 'user'])
s.default(s.enum(['admin', 'user']), 'user')
s.nullable(s.string())

s is re-exported from all adapter packages for convenience:

const { s } = require('doctreen/express');
const { s } = require('doctreen/fastify');
const { s } = require('doctreen/hono');
const { s } = require('doctreen/koa');
const { s } = require('doctreen/nest');
from doctreen import s

s.string()
s.number()
s.boolean()
s.null()
s.unknown()

s.object({
    "id": s.number(),
    "name": s.string(),
    "role": s.default(s.enum(["admin", "user"]), "user"),
    "deletedAt": s.nullable(s.optional(s.string())),
})

s.array(s.string())
s.optional(s.string())

The builder mirrors the Node s helper method-for-method — the conformance suite keeps the OpenAPI output byte-identical.

use Doctreen\Schema\S;

S::string();
S::number();
S::boolean();
S::null();
S::unknown();

S::object([
    'id'   => S::number(),
    'name' => S::string(),
    'bio'  => S::optional(S::string()),
]);

S::array(S::string());
S::optional(S::string());

Builder vs a validating schema library

s / S:: builderZod (Node)Pydantic (Python)
Used for docs / OpenAPIYesYesYes
Used for runtime validationNo (descriptive only)YesYes
DependencyNonezodpydantic
Refinements / custom validatorsNoYesYes

Mixed routes work fine — only validating schemas (Zod on Node, Pydantic on Python) validate when validate: true; builder-declared routes pass through. A builder node describes a shape; checking against a description would either reject valid payloads or wave invalid ones through.

On this page