DocTreen

Named schemas

Register schemas under a name, reference them anywhere, dedup in OpenAPI export.

Register a schema under a name with defineSchema and reference it from JSDoc with {TypeName} or {TypeName[]}:

const { defineSchema, s } = require('doctreen');

defineSchema('User', s.object({
  id:     s.number(),
  name:   s.string(),
  email:  s.string(),
  active: s.boolean(),
  bio:    s.optional(s.string()),
}));

app.get('/users', function (req, res) {
  /**
   * @description List all users
   * @returns {User[]}
   */
  res.json([]);
});

app.get('/users/:id', function (req, res) {
  /**
   * @description Get a user by ID
   * @returns {User}
   */
  res.json({ id: 1, name: 'Alice', email: 'alice@example.com', active: true });
});

defineSchema is also re-exported from all adapter packages.

Zod-defined named schemas v1.15

defineSchema works with Zod schemas, not just the s.* builder. Both are first-class:

const { defineSchema } = require('doctreen/express');
const { z } = require('zod');

const User = defineSchema('User', z.object({
  id:   z.number(),
  name: z.string(),
}));

// reuse `User` in any route's request/response → one components.schemas.User,
// referenced by $ref everywhere it appears

Previously named schemas were matched by object identity, which the Zod → SchemaNode conversion breaks — so a Zod-defined defineSchema('User', …) used in a route fell through to anonymous Schema1 / Schema2 types. Now Zod-defined named schemas emit $ref: '#/components/schemas/User' in the OpenAPI export at the top level, nested inside other objects, and inside arrays.

This makes Zod-defined named schemas the recommended way to express recursive schemas: a self-reference emits a terminating $ref instead of expanding forever.

With defineRoute (Zod or s)

const { s, defineSchema } = require('doctreen');
const { defineRoute } = require('doctreen/express');

const User = defineSchema('User', s.object({
  id:    s.number(),
  name:  s.string(),
  email: s.string(),
}));

app.post('/users',    defineRoute(handler, { request: { body: User }, response: User }));
app.get('/users/:id', defineRoute(handler, { response: User }));

Every route that references the same object will share a single $ref: '#/components/schemas/User' entry in the exported OpenAPI spec.

$ref dedup v1.11

Anonymous object schemas with three or more properties that appear two or more times in the spec are also auto-promoted under stable Schema1, Schema2, … names — you get the dedup benefit without naming every shape.

On this page