12k

OpenAPI Integration

Use json-render to generate dynamic forms and UIs from OpenAPI/Swagger schemas.

Concept: This page demonstrates how json-render can support OpenAPI schemas. The examples are illustrative and may require adaptation for production use.

Why OpenAPI?#

OpenAPI specifications describe your API's endpoints, request bodies, and response schemas. By converting OpenAPI schemas to json-render specs, you can:

  • Automatically generate forms for API endpoints
  • Display API responses with type-aware rendering
  • Keep your UI in sync with your API schema
  • Let AI generate UIs that match your API contracts

Example OpenAPI Schema#

A typical OpenAPI schema for a request body:

{
  "openapi": "3.0.0",
  "paths": {
    "/users": {
      "post": {
        "summary": "Create a new user",
        "operationId": "createUser",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateUserRequest"
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "CreateUserRequest": {
        "type": "object",
        "required": ["email", "name"],
        "properties": {
          "name": {
            "type": "string",
            "description": "User's full name",
            "minLength": 1,
            "maxLength": 100
          },
          "email": {
            "type": "string",
            "format": "email",
            "description": "User's email address"
          },
          "age": {
            "type": "integer",
            "minimum": 0,
            "maximum": 150,
            "description": "User's age"
          },
          "role": {
            "type": "string",
            "enum": ["admin", "user", "guest"],
            "default": "user",
            "description": "User's role"
          },
          "preferences": {
            "type": "object",
            "properties": {
              "newsletter": {
                "type": "boolean",
                "default": false
              },
              "theme": {
                "type": "string",
                "enum": ["light", "dark", "system"]
              }
            }
          }
        }
      }
    }
  }
}

Define an OpenAPI-to-UI Catalog#

Create components that map to OpenAPI data types:

import { defineCatalog } from '@json-render/core';
import { schema } from '@json-render/react/schema';
import { z } from 'zod';

export const openapiCatalog = defineCatalog(schema, {
  components: {
    Form: {
      description: 'API form container',
      props: z.object({
        operationId: z.string(),
        endpoint: z.string(),
        method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']),
        title: z.string().optional(),
        description: z.string().optional(),
      }),
    },
    StringField: {
      description: 'String input field',
      props: z.object({
        name: z.string(),
        label: z.string(),
        description: z.string().optional(),
        required: z.boolean().optional(),
        format: z.enum(['text', 'email', 'uri', 'uuid', 'date', 'date-time', 'password']).optional(),
        minLength: z.number().optional(),
        maxLength: z.number().optional(),
        pattern: z.string().optional(),
        placeholder: z.string().optional(),
        defaultValue: z.string().optional(),
      }),
    },
    NumberField: {
      description: 'Number input field',
      props: z.object({
        name: z.string(),
        label: z.string(),
        description: z.string().optional(),
        required: z.boolean().optional(),
        type: z.enum(['integer', 'number']).optional(),
        minimum: z.number().optional(),
        maximum: z.number().optional(),
        defaultValue: z.number().optional(),
      }),
    },
    BooleanField: {
      description: 'Boolean toggle field',
      props: z.object({
        name: z.string(),
        label: z.string(),
        description: z.string().optional(),
        defaultValue: z.boolean().optional(),
      }),
    },
    EnumField: {
      description: 'Enum selection field',
      props: z.object({
        name: z.string(),
        label: z.string(),
        description: z.string().optional(),
        required: z.boolean().optional(),
        options: z.array(z.object({
          value: z.string(),
          label: z.string().optional(),
        })),
        defaultValue: z.string().optional(),
      }),
    },
    ObjectField: {
      description: 'Nested object group',
      props: z.object({
        name: z.string(),
        label: z.string(),
        description: z.string().optional(),
        collapsible: z.boolean().optional(),
      }),
    },
  },
  actions: {
    submit: {
      description: 'Submit form to API endpoint',
      params: z.object({ operationId: z.string() }),
    },
    reset: {
      description: 'Reset form to defaults',
      params: z.object({}),
    },
  },
});

Convert OpenAPI Schema to Spec#

Transform OpenAPI schemas into json-render specs by recursively walking the schema properties and mapping each type to the corresponding catalog component. The converter handles nested objects, enums, arrays, and all primitive types.

Usage Example#

'use client';

import { OpenAPIForm } from './openapi-form';
import { operationToSpec } from './openapi-to-spec';

// Your OpenAPI schema (typically loaded from your API)
const createUserSchema = {
  type: 'object',
  required: ['email', 'name'],
  properties: {
    name: { type: 'string', description: "User's full name" },
    email: { type: 'string', format: 'email', description: "User's email" },
    age: { type: 'integer', minimum: 0, maximum: 150 },
    role: { type: 'string', enum: ['admin', 'user', 'guest'], default: 'user' },
  },
};

// Convert to spec
const spec = operationToSpec(
  'createUser',
  'POST',
  '/api/users',
  createUserSchema,
  'Create User',
  'Add a new user to the system',
);

export function CreateUserForm() {
  const handleSubmit = async (data: Record<string, unknown>) => {
    const response = await fetch('/api/users', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(data),
    });

    if (response.ok) {
      console.log('User created!');
    }
  };

  return <OpenAPIForm spec={spec} onSubmit={handleSubmit} />;
}

Auto-generating from OpenAPI Document#

Load and parse an OpenAPI document to generate forms for all operations:

import SwaggerParser from '@apidevtools/swagger-parser';
import { operationToSpec } from './openapi-to-spec';

export async function loadOpenAPISpecs(specUrl: string) {
  const api = await SwaggerParser.dereference(specUrl);
  const specs: Record<string, any> = {};

  for (const [path, methods] of Object.entries(api.paths)) {
    for (const [method, operation] of Object.entries(methods)) {
      if (!operation.requestBody?.content?.['application/json']?.schema) continue;

      const schema = operation.requestBody.content['application/json'].schema;
      const operationId = operation.operationId || `${method}_${path.replace(/\//g, '_')}`;

      specs[operationId] = operationToSpec(
        operationId,
        method,
        path,
        schema,
        operation.summary,
        operation.description,
      );
    }
  }

  return specs;
}

// Usage
const specs = await loadOpenAPISpecs('https://api.example.com/openapi.json');
// specs.createUser, specs.updateUser, etc.

Next#

Learn about streaming for progressive UI rendering.