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.