Skip to content

📋 Structured Output Made Simple

Right now, you have chat, images, audio, files, speech, vision, voice interaction, and MCP integration working in your application. But what if your AI could return perfectly structured, validated data every time?

Structured output eliminates unpredictable AI responses. Instead of parsing free-form text, your AI returns JSON data that matches exact schemas, making it perfect for APIs, databases, and reliable data processing.

You’re about to learn exactly how to add structured output with Zod validation to your existing application.


🧠 Step 1: Understanding Structured Output

Section titled “🧠 Step 1: Understanding Structured Output”

Before we write any code, let’s understand what structured output actually means and why it’s different from what you’ve built before.

Structured output is like having your AI speak in perfect data formats. Instead of returning natural language that you need to parse, your AI returns validated JSON objects that match exact schemas you define.

Real-world analogy: It’s like the difference between asking someone to “tell me about this person” (free-form response) versus asking them to “fill out this exact form with name, age, email, and address fields” (structured response).

Why Structured Output vs. Your Existing Features

Section titled “Why Structured Output vs. Your Existing Features”

You already have powerful AI capabilities, but structured output is different:

💬 Regular Chat - AI returns natural language text (unpredictable format) 🎤 Voice/Audio - AI processes and generates audio (specific format but not data)
📋 Structured Output - AI returns validated JSON data (guaranteed format)

The key difference: Structured output guarantees your AI responses match exact data schemas, making them perfect for programmatic use, database storage, and API integration.

Think about all the times you need reliable data formats:

  • Form processing - Extract structured data from documents
  • API responses - Return consistent JSON for frontend applications
  • Database operations - Generate records that match database schemas
  • Data analysis - Create standardized reports and analytics
  • Workflow automation - Trigger actions based on structured data

Without structured output, you must:

  1. Parse unpredictable AI text responses (error-prone)
  2. Handle various response formats (inconsistent)
  3. Validate data manually (time-consuming)
  4. Deal with parsing failures (unreliable)

With structured output, your AI guarantees valid, structured data that matches your exact requirements.

Your structured output will use Zod for schema validation:

📋 Zod Schema Validation - The Data Validator

  • Best for: Defining and validating TypeScript-first schemas
  • Strengths: Type safety, runtime validation, error handling
  • Use cases: API responses, form validation, data processing
  • Think of it as: Your data format enforcer

Key capabilities:

  • Type-safe schemas with automatic TypeScript inference
  • Runtime validation ensuring data matches expected format
  • Error reporting with detailed validation messages
  • Flexible schemas supporting complex nested data structures

🔧 Step 2: Adding Structured Output to Your Backend

Section titled “🔧 Step 2: Adding Structured Output to Your Backend”

Let’s add structured output to your existing backend using the same patterns you learned in previous modules. We’ll add new routes to handle structured data generation with Zod validation.

Building on your foundation: You already have a working Node.js server with OpenAI integration. We’re simply adding structured output capabilities to guarantee reliable data formats.

Step 2A: Understanding Structured Output State

Section titled “Step 2A: Understanding Structured Output State”

Before writing code, let’s understand what data our structured output system needs to manage:

// 🧠 STRUCTURED OUTPUT STATE CONCEPTS:
// 1. Schema Definitions - Zod schemas defining expected data formats
// 2. Validation Rules - Type checking and constraint enforcement
// 3. Data Generation - AI responses matching exact schemas
// 4. Error Handling - Schema validation failures and recovery
// 5. Format Options - Different output formats and validation levels

Key structured output concepts:

  • Schema Design: Creating Zod schemas that define exact data structures
  • Validation Pipeline: Ensuring AI responses match schema requirements
  • Type Safety: Leveraging TypeScript for compile-time schema checking
  • Error Recovery: Handling validation failures and retry logic

First, add the structured output dependencies to your backend. In your backend folder, run:

Terminal window
npm install zod

What this package does:

  • zod: TypeScript-first schema validation with static type inference

Step 2C: Adding the Structured Output Routes

Section titled “Step 2C: Adding the Structured Output Routes”

Add these new endpoints to your existing index.js file, right after your MCP integration routes:

import { z } from 'zod';
// 📋 SCHEMA DEFINITIONS: Predefined Zod schemas for common use cases
// Personal information schema
const PersonSchema = z.object({
name: z.string().min(1, "Name is required"),
age: z.number().int().min(0).max(150),
email: z.string().email("Invalid email format"),
phone: z.string().optional(),
address: z.object({
street: z.string(),
city: z.string(),
state: z.string(),
zipCode: z.string(),
country: z.string()
}).optional()
});
// Event schema
const EventSchema = z.object({
title: z.string().min(1, "Title is required"),
description: z.string().optional(),
startDate: z.string().datetime("Invalid datetime format"),
endDate: z.string().datetime("Invalid datetime format"),
location: z.string().optional(),
attendees: z.array(z.string()).default([]),
category: z.enum(["meeting", "social", "work", "personal", "other"]),
priority: z.enum(["low", "medium", "high"]).default("medium"),
reminders: z.array(z.object({
time: z.string().datetime(),
type: z.enum(["email", "push", "sms"])
})).default([])
});
// Product schema
const ProductSchema = z.object({
name: z.string().min(1, "Product name is required"),
description: z.string(),
price: z.number().positive("Price must be positive"),
category: z.string(),
inStock: z.boolean(),
specifications: z.record(z.string()).optional(),
tags: z.array(z.string()).default([]),
rating: z.number().min(0).max(5).optional(),
reviews: z.array(z.object({
author: z.string(),
rating: z.number().min(1).max(5),
comment: z.string(),
date: z.string().datetime()
})).default([])
});
// Task schema
const TaskSchema = z.object({
title: z.string().min(1, "Task title is required"),
description: z.string().optional(),
status: z.enum(["todo", "in_progress", "completed", "cancelled"]).default("todo"),
priority: z.enum(["low", "medium", "high", "urgent"]).default("medium"),
dueDate: z.string().datetime().optional(),
assignee: z.string().optional(),
tags: z.array(z.string()).default([]),
subtasks: z.array(z.object({
title: z.string(),
completed: z.boolean().default(false)
})).default([]),
estimatedHours: z.number().positive().optional(),
actualHours: z.number().positive().optional()
});
// Analysis schema
const AnalysisSchema = z.object({
summary: z.string().min(10, "Summary must be at least 10 characters"),
keyPoints: z.array(z.string()).min(1, "At least one key point required"),
sentiment: z.enum(["positive", "negative", "neutral", "mixed"]),
confidence: z.number().min(0).max(1),
categories: z.array(z.string()).default([]),
entities: z.array(z.object({
text: z.string(),
type: z.enum(["person", "organization", "location", "date", "other"]),
confidence: z.number().min(0).max(1)
})).default([]),
recommendations: z.array(z.string()).default([])
});
// 🔧 HELPER FUNCTIONS: Schema utilities
// Convert Zod schema to OpenAI function format
const zodToOpenAIFunction = (schema, name, description) => {
const schemaObject = schema._def;
// Convert Zod schema to JSON Schema format
const convertZodToJsonSchema = (zodSchema) => {
const def = zodSchema._def;
switch (def.typeName) {
case 'ZodString':
return { type: 'string' };
case 'ZodNumber':
return { type: 'number' };
case 'ZodBoolean':
return { type: 'boolean' };
case 'ZodArray':
return {
type: 'array',
items: convertZodToJsonSchema(def.type)
};
case 'ZodObject':
const properties = {};
const required = [];
for (const [key, value] of Object.entries(def.shape())) {
properties[key] = convertZodToJsonSchema(value);
if (!value.isOptional()) {
required.push(key);
}
}
return {
type: 'object',
properties,
required: required.length > 0 ? required : undefined
};
case 'ZodEnum':
return {
type: 'string',
enum: def.values
};
default:
return { type: 'string' }; // Fallback
}
};
return {
type: "function",
function: {
name,
description,
parameters: convertZodToJsonSchema(schema)
}
};
};
// Get available schemas
const getAvailableSchemas = () => {
return {
person: {
schema: PersonSchema,
description: "Extract or generate person information with contact details"
},
event: {
schema: EventSchema,
description: "Create calendar events with dates, locations, and attendees"
},
product: {
schema: ProductSchema,
description: "Generate product information with pricing and specifications"
},
task: {
schema: TaskSchema,
description: "Create project tasks with status, priority, and deadlines"
},
analysis: {
schema: AnalysisSchema,
description: "Perform content analysis with sentiment and key insights"
}
};
};
// 📋 STRUCTURED OUTPUT ENDPOINTS: Add these to your existing server
// Get available schemas
app.get("/api/structured/schemas", (req, res) => {
try {
const schemas = getAvailableSchemas();
const schemaList = Object.keys(schemas).map(key => ({
name: key,
description: schemas[key].description,
example_fields: Object.keys(schemas[key].schema.shape)
}));
res.json({
success: true,
schemas: schemaList,
total_schemas: schemaList.length
});
} catch (error) {
console.error("Schema listing error:", error);
res.status(500).json({
error: "Failed to list schemas",
details: error.message,
success: false
});
}
});
// Generate structured output
app.post("/api/structured/generate", async (req, res) => {
try {
const {
prompt,
schema_name,
custom_schema = null,
max_retries = 3,
temperature = 0.1
} = req.body;
if (!prompt) {
return res.status(400).json({
error: "Prompt is required",
success: false
});
}
console.log(`📋 Structured output: ${prompt.substring(0, 50)}... (Schema: ${schema_name})`);
// Get schema
let targetSchema;
let schemaDescription;
if (custom_schema) {
try {
// Parse custom schema (simplified - you might want more robust parsing)
targetSchema = z.object(custom_schema);
schemaDescription = "Custom schema";
} catch (error) {
return res.status(400).json({
error: "Invalid custom schema",
details: error.message,
success: false
});
}
} else if (schema_name) {
const schemas = getAvailableSchemas();
if (!schemas[schema_name]) {
return res.status(400).json({
error: `Schema '${schema_name}' not found`,
available_schemas: Object.keys(schemas),
success: false
});
}
targetSchema = schemas[schema_name].schema;
schemaDescription = schemas[schema_name].description;
} else {
return res.status(400).json({
error: "Either schema_name or custom_schema is required",
success: false
});
}
// Convert schema to OpenAI function format
const functionSchema = zodToOpenAIFunction(
targetSchema,
`generate_${schema_name || 'custom'}`,
`Generate structured data: ${schemaDescription}`
);
let attempt = 0;
let validationErrors = [];
while (attempt < max_retries) {
try {
// 🎯 STRUCTURED GENERATION: Create OpenAI chat with function calling
const response = await openai.chat.completions.create({
model: "gpt-4",
messages: [
{
role: "system",
content: `You are a data generation expert. Generate structured data that exactly matches the provided schema. Be accurate, detailed, and ensure all required fields are included with appropriate values.
Schema: ${schemaDescription}
Format: JSON object matching the exact schema requirements`
},
{
role: "user",
content: prompt
}
],
tools: [functionSchema],
tool_choice: { type: "function", function: { name: functionSchema.function.name } },
temperature
});
// Extract function call result
const toolCall = response.choices[0].message.tool_calls?.[0];
if (!toolCall) {
throw new Error("No function call generated");
}
const generatedData = JSON.parse(toolCall.function.arguments);
// 🛡️ VALIDATION: Validate against schema
const validatedData = targetSchema.parse(generatedData);
// 📤 SUCCESS RESPONSE: Return validated structured data
res.json({
success: true,
data: validatedData,
schema_used: schema_name || 'custom',
validation: {
passed: true,
attempts: attempt + 1,
errors: []
},
metadata: {
model: "gpt-4",
temperature,
timestamp: new Date().toISOString()
}
});
return; // Exit successfully
} catch (error) {
attempt++;
if (error instanceof z.ZodError) {
// Schema validation error
validationErrors.push({
attempt,
type: 'validation',
errors: error.errors.map(err => ({
path: err.path.join('.'),
message: err.message,
code: err.code
}))
});
console.warn(`Validation attempt ${attempt} failed:`, error.errors);
if (attempt >= max_retries) {
return res.status(422).json({
error: "Data validation failed after maximum retries",
validation: {
passed: false,
attempts: attempt,
errors: validationErrors
},
success: false
});
}
} else {
// Generation error
console.error(`Generation attempt ${attempt} failed:`, error);
if (attempt >= max_retries) {
return res.status(500).json({
error: "Failed to generate structured data",
details: error.message,
validation: {
passed: false,
attempts: attempt,
errors: validationErrors
},
success: false
});
}
}
}
}
} catch (error) {
console.error("Structured output error:", error);
res.status(500).json({
error: "Failed to process structured output request",
details: error.message,
success: false
});
}
});
// Validate existing data against schema
app.post("/api/structured/validate", (req, res) => {
try {
const { data, schema_name, custom_schema = null } = req.body;
if (!data) {
return res.status(400).json({
error: "Data is required",
success: false
});
}
// Get schema
let targetSchema;
if (custom_schema) {
try {
targetSchema = z.object(custom_schema);
} catch (error) {
return res.status(400).json({
error: "Invalid custom schema",
details: error.message,
success: false
});
}
} else if (schema_name) {
const schemas = getAvailableSchemas();
if (!schemas[schema_name]) {
return res.status(400).json({
error: `Schema '${schema_name}' not found`,
available_schemas: Object.keys(schemas),
success: false
});
}
targetSchema = schemas[schema_name].schema;
} else {
return res.status(400).json({
error: "Either schema_name or custom_schema is required",
success: false
});
}
// Validate data
try {
const validatedData = targetSchema.parse(data);
res.json({
success: true,
valid: true,
data: validatedData,
schema_used: schema_name || 'custom',
errors: []
});
} catch (error) {
if (error instanceof z.ZodError) {
res.status(422).json({
success: true,
valid: false,
errors: error.errors.map(err => ({
path: err.path.join('.'),
message: err.message,
code: err.code,
received: err.received
})),
schema_used: schema_name || 'custom'
});
} else {
throw error;
}
}
} catch (error) {
console.error("Validation error:", error);
res.status(500).json({
error: "Failed to validate data",
details: error.message,
success: false
});
}
});
// Transform data between schemas
app.post("/api/structured/transform", async (req, res) => {
try {
const {
data,
source_schema,
target_schema,
transformation_prompt = "Transform the data to match the target schema while preserving as much information as possible."
} = req.body;
if (!data || !source_schema || !target_schema) {
return res.status(400).json({
error: "Data, source_schema, and target_schema are required",
success: false
});
}
console.log(`📋 Transforming data from ${source_schema} to ${target_schema}`);
const schemas = getAvailableSchemas();
// Validate source schema
if (!schemas[source_schema]) {
return res.status(400).json({
error: `Source schema '${source_schema}' not found`,
success: false
});
}
// Validate target schema
if (!schemas[target_schema]) {
return res.status(400).json({
error: `Target schema '${target_schema}' not found`,
success: false
});
}
// Validate input data against source schema
try {
schemas[source_schema].schema.parse(data);
} catch (error) {
return res.status(422).json({
error: "Input data does not match source schema",
validation_errors: error.errors,
success: false
});
}
// Transform using structured output
const transformResponse = await fetch(`http://localhost:${port}/api/structured/generate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
prompt: `${transformation_prompt}\n\nSource data (${source_schema}): ${JSON.stringify(data, null, 2)}`,
schema_name: target_schema,
temperature: 0.1
})
});
const transformData = await transformResponse.json();
if (!transformResponse.ok) {
throw new Error(transformData.error || 'Transformation failed');
}
res.json({
success: true,
original_data: data,
transformed_data: transformData.data,
source_schema,
target_schema,
validation: transformData.validation,
timestamp: new Date().toISOString()
});
} catch (error) {
console.error("Transformation error:", error);
res.status(500).json({
error: "Failed to transform data",
details: error.message,
success: false
});
}
});

Function breakdown:

  1. Schema definitions - Predefined Zod schemas for common data structures
  2. Schema conversion - Transform Zod schemas to OpenAI function format
  3. Data generation - Create structured data matching exact schemas
  4. Validation pipeline - Ensure generated data meets schema requirements
  5. Error handling - Retry logic and detailed validation error reporting

🔧 Step 3: Building the React Structured Output Component

Section titled “🔧 Step 3: Building the React Structured Output Component”

Now let’s create a React component for structured output using the same patterns from your existing components.

Step 3A: Creating the Structured Output Component

Section titled “Step 3A: Creating the Structured Output Component”

Create a new file src/StructuredOutput.jsx:

import { useState, useEffect } from "react";
import { FileText, Check, X, Download, Upload, RefreshCw, Code, Database } from "lucide-react";
function StructuredOutput() {
// 🧠 STATE: Structured output data management
const [schemas, setSchemas] = useState([]); // Available schemas
const [selectedSchema, setSelectedSchema] = useState(""); // Current schema
const [prompt, setPrompt] = useState(""); // Generation prompt
const [isGenerating, setIsGenerating] = useState(false); // Generation status
const [result, setResult] = useState(null); // Generation result
const [error, setError] = useState(null); // Error messages
const [validationData, setValidationData] = useState(""); // Data for validation
const [isValidating, setIsValidating] = useState(false); // Validation status
const [validationResult, setValidationResult] = useState(null); // Validation result
const [activeTab, setActiveTab] = useState("generate"); // Active tab
const [maxRetries, setMaxRetries] = useState(3); // Generation retries
const [temperature, setTemperature] = useState(0.1); // Generation creativity
// 🔧 FUNCTIONS: Structured output logic engine
// Load available schemas
const loadSchemas = async () => {
try {
const response = await fetch("http://localhost:8000/api/structured/schemas");
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Failed to load schemas');
}
setSchemas(data.schemas);
if (data.schemas.length > 0 && !selectedSchema) {
setSelectedSchema(data.schemas[0].name);
}
} catch (error) {
console.error('Failed to load schemas:', error);
setError(error.message || 'Could not load schemas');
}
};
// Generate structured data
const generateStructuredData = async () => {
if (!prompt.trim() || !selectedSchema) {
setError('Both prompt and schema selection are required');
return;
}
setIsGenerating(true);
setError(null);
setResult(null);
try {
const response = await fetch("http://localhost:8000/api/structured/generate", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
prompt: prompt.trim(),
schema_name: selectedSchema,
max_retries: maxRetries,
temperature: temperature
})
});
const data = await response.json();
if (!response.ok) {
throw new Error(data.error || 'Failed to generate structured data');
}
setResult(data);
} catch (error) {
console.error('Generation failed:', error);
setError(error.message || 'Could not generate structured data');
} finally {
setIsGenerating(false);
}
};
// Validate data against schema
const validateData = async () => {
if (!validationData.trim() || !selectedSchema) {
setError('Both data and schema selection are required for validation');
return;
}
setIsValidating(true);
setError(null);
setValidationResult(null);
try {
// Parse JSON data
let parsedData;
try {
parsedData = JSON.parse(validationData);
} catch (parseError) {
throw new Error('Invalid JSON format');
}
const response = await fetch("http://localhost:8000/api/structured/validate", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
data: parsedData,
schema_name: selectedSchema
})
});
const data = await response.json();
if (!response.ok && response.status !== 422) {
throw new Error(data.error || 'Failed to validate data');
}
setValidationResult(data);
} catch (error) {
console.error('Validation failed:', error);
setError(error.message || 'Could not validate data');
} finally {
setIsValidating(false);
}
};
// Download result as JSON
const downloadResult = (data, filename) => {
const element = document.createElement('a');
const file = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' });
element.href = URL.createObjectURL(file);
element.download = filename;
document.body.appendChild(element);
element.click();
document.body.removeChild(element);
};
// Clear current results
const clearResults = () => {
setResult(null);
setValidationResult(null);
setError(null);
};
// Handle Enter key press in textarea
const handleKeyPress = (e) => {
if (e.key === 'Enter' && e.ctrlKey) {
e.preventDefault();
if (activeTab === 'generate') {
generateStructuredData();
} else if (activeTab === 'validate') {
validateData();
}
}
};
// Load schemas on component mount
useEffect(() => {
loadSchemas();
}, []);
// Example prompts for different schemas
const getExamplePrompt = (schemaName) => {
const examples = {
person: "Create a person profile for a software engineer named Alice Johnson who lives in San Francisco",
event: "Create a team meeting event for next Friday at 2 PM in the main conference room",
product: "Create a product listing for a wireless Bluetooth headphone with noise cancellation",
task: "Create a high-priority task for completing the quarterly financial report due next week",
analysis: "Analyze this customer feedback: 'The product is amazing but the delivery was slow and customer service was unhelpful'"
};
return examples[schemaName] || "";
};
// 🎨 UI: Interface components
return (
<div className="min-h-screen bg-gradient-to-br from-purple-50 to-indigo-50 flex items-center justify-center p-4">
<div className="bg-white rounded-2xl shadow-2xl w-full max-w-6xl flex flex-col overflow-hidden">
{/* Header */}
<div className="bg-gradient-to-r from-purple-600 to-indigo-600 text-white p-6">
<div className="flex items-center space-x-3">
<div className="w-10 h-10 bg-white bg-opacity-20 rounded-full flex items-center justify-center">
<FileText className="w-5 h-5" />
</div>
<div>
<h1 className="text-xl font-bold">📋 Structured Output</h1>
<p className="text-purple-100 text-sm">Generate and validate structured data with Zod schemas!</p>
</div>
</div>
</div>
{/* Tab Navigation */}
<div className="border-b border-gray-200">
<nav className="flex">
<button
onClick={() => setActiveTab('generate')}
className={`px-6 py-3 font-medium text-sm border-b-2 transition-colors duration-200 ${
activeTab === 'generate'
? 'border-purple-500 text-purple-600'
: 'border-transparent text-gray-500 hover:text-gray-700'
}`}
>
<Database className="w-4 h-4 inline mr-2" />
Generate Data
</button>
<button
onClick={() => setActiveTab('validate')}
className={`px-6 py-3 font-medium text-sm border-b-2 transition-colors duration-200 ${
activeTab === 'validate'
? 'border-purple-500 text-purple-600'
: 'border-transparent text-gray-500 hover:text-gray-700'
}`}
>
<Check className="w-4 h-4 inline mr-2" />
Validate Data
</button>
</nav>
</div>
<div className="flex flex-1">
{/* Schema Sidebar */}
<div className="w-1/3 border-r border-gray-200 p-6">
<h3 className="font-semibold text-gray-900 mb-4 flex items-center">
<Code className="w-5 h-5 mr-2 text-purple-600" />
Available Schemas ({schemas.length})
</h3>
{/* Schema Selection */}
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-2">
Select Schema
</label>
<select
value={selectedSchema}
onChange={(e) => setSelectedSchema(e.target.value)}
className="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500"
>
<option value="">Choose a schema...</option>
{schemas.map((schema) => (
<option key={schema.name} value={schema.name}>
{schema.name} - {schema.description}
</option>
))}
</select>
</div>
{/* Schema Details */}
{selectedSchema && (
<div className="mb-4 p-4 bg-gray-50 rounded-lg">
{schemas.find(s => s.name === selectedSchema) && (
<div>
<h4 className="font-medium text-gray-900 mb-2">
{selectedSchema.charAt(0).toUpperCase() + selectedSchema.slice(1)} Schema
</h4>
<p className="text-sm text-gray-600 mb-3">
{schemas.find(s => s.name === selectedSchema).description}
</p>
<div>
<p className="text-xs font-medium text-gray-700 mb-1">Fields:</p>
<div className="flex flex-wrap gap-1">
{schemas.find(s => s.name === selectedSchema).example_fields.map((field) => (
<span
key={field}
className="px-2 py-1 bg-purple-100 text-purple-700 rounded text-xs"
>
{field}
</span>
))}
</div>
</div>
</div>
)}
</div>
)}
{/* Generation Settings */}
{activeTab === 'generate' && (
<div className="space-y-4">
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Max Retries: {maxRetries}
</label>
<input
type="range"
min="1"
max="5"
value={maxRetries}
onChange={(e) => setMaxRetries(parseInt(e.target.value))}
className="w-full"
/>
</div>
<div>
<label className="block text-sm font-medium text-gray-700 mb-2">
Temperature: {temperature}
</label>
<input
type="range"
min="0"
max="1"
step="0.1"
value={temperature}
onChange={(e) => setTemperature(parseFloat(e.target.value))}
className="w-full"
/>
<div className="flex justify-between text-xs text-gray-500 mt-1">
<span>Strict</span>
<span>Creative</span>
</div>
</div>
</div>
)}
<button
onClick={loadSchemas}
className="w-full mt-4 px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors duration-200 text-sm"
>
<RefreshCw className="w-4 h-4 inline mr-2" />
Refresh Schemas
</button>
</div>
{/* Main Content */}
<div className="flex-1 flex flex-col">
{/* Error Display */}
{error && (
<div className="p-4 bg-red-50 border-b border-red-200">
<p className="text-red-700 text-sm">
<strong>Error:</strong> {error}
</p>
</div>
)}
{/* Generate Tab */}
{activeTab === 'generate' && (
<div className="flex-1 p-6">
<div className="mb-4">
<div className="flex items-center justify-between mb-2">
<label className="block text-sm font-medium text-gray-700">
Generation Prompt
</label>
{selectedSchema && (
<button
onClick={() => setPrompt(getExamplePrompt(selectedSchema))}
className="px-3 py-1 bg-purple-100 text-purple-700 rounded-lg hover:bg-purple-200 transition-colors duration-200 text-sm"
>
Use Example
</button>
)}
</div>
<textarea
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
onKeyDown={handleKeyPress}
placeholder="Describe what data you want to generate..."
disabled={isGenerating || !selectedSchema}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 resize-none disabled:bg-gray-100"
rows="4"
/>
<p className="text-xs text-gray-500 mt-1">
Ctrl+Enter to generate • Be specific about the data you want
</p>
</div>
<div className="flex items-center space-x-3 mb-6">
<button
onClick={generateStructuredData}
disabled={isGenerating || !prompt.trim() || !selectedSchema}
className="px-6 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200"
>
{isGenerating ? (
<>
<RefreshCw className="w-4 h-4 animate-spin inline mr-2" />
Generating...
</>
) : (
<>
<Database className="w-4 h-4 inline mr-2" />
Generate Data
</>
)}
</button>
{result && (
<button
onClick={clearResults}
className="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors duration-200"
>
Clear
</button>
)}
</div>
{/* Generation Result */}
{result && (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="font-semibold text-gray-900">Generated Data</h3>
<div className="flex items-center space-x-2">
<span className={`px-2 py-1 rounded text-xs ${
result.validation.passed
? 'bg-green-100 text-green-700'
: 'bg-red-100 text-red-700'
}`}>
{result.validation.passed ? 'Valid' : 'Invalid'}
</span>
<span className="text-xs text-gray-500">
{result.validation.attempts} attempt{result.validation.attempts !== 1 ? 's' : ''}
</span>
<button
onClick={() => downloadResult(result.data, `${selectedSchema}-${Date.now()}.json`)}
className="px-3 py-1 bg-purple-100 text-purple-700 rounded-lg hover:bg-purple-200 transition-colors duration-200 text-sm"
>
<Download className="w-4 h-4 inline mr-1" />
Download
</button>
</div>
</div>
<div className="bg-gray-50 rounded-lg p-4">
<pre className="text-sm text-gray-800 overflow-x-auto">
{JSON.stringify(result.data, null, 2)}
</pre>
</div>
{result.validation.errors.length > 0 && (
<div className="bg-red-50 rounded-lg p-4">
<h4 className="font-medium text-red-900 mb-2">Validation Errors</h4>
<div className="space-y-2">
{result.validation.errors.map((errorGroup, groupIndex) => (
<div key={groupIndex}>
<p className="text-sm font-medium text-red-800">
Attempt {errorGroup.attempt}:
</p>
<ul className="text-sm text-red-700 ml-4">
{errorGroup.errors.map((error, errorIndex) => (
<li key={errorIndex}>
<strong>{error.path}:</strong> {error.message}
</li>
))}
</ul>
</div>
))}
</div>
</div>
)}
</div>
)}
</div>
)}
{/* Validate Tab */}
{activeTab === 'validate' && (
<div className="flex-1 p-6">
<div className="mb-4">
<label className="block text-sm font-medium text-gray-700 mb-2">
JSON Data to Validate
</label>
<textarea
value={validationData}
onChange={(e) => setValidationData(e.target.value)}
onKeyDown={handleKeyPress}
placeholder="Paste your JSON data here..."
disabled={isValidating || !selectedSchema}
className="w-full px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-purple-500 resize-none disabled:bg-gray-100 font-mono text-sm"
rows="8"
/>
<p className="text-xs text-gray-500 mt-1">
Ctrl+Enter to validate • Must be valid JSON format
</p>
</div>
<div className="flex items-center space-x-3 mb-6">
<button
onClick={validateData}
disabled={isValidating || !validationData.trim() || !selectedSchema}
className="px-6 py-2 bg-purple-600 text-white rounded-lg hover:bg-purple-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors duration-200"
>
{isValidating ? (
<>
<RefreshCw className="w-4 h-4 animate-spin inline mr-2" />
Validating...
</>
) : (
<>
<Check className="w-4 h-4 inline mr-2" />
Validate Data
</>
)}
</button>
{validationResult && (
<button
onClick={clearResults}
className="px-4 py-2 bg-gray-100 text-gray-700 rounded-lg hover:bg-gray-200 transition-colors duration-200"
>
Clear
</button>
)}
</div>
{/* Validation Result */}
{validationResult && (
<div className="space-y-4">
<div className="flex items-center justify-between">
<h3 className="font-semibold text-gray-900">Validation Result</h3>
<span className={`px-3 py-1 rounded-lg text-sm font-medium ${
validationResult.valid
? 'bg-green-100 text-green-700'
: 'bg-red-100 text-red-700'
}`}>
{validationResult.valid ? (
<>
<Check className="w-4 h-4 inline mr-1" />
Valid
</>
) : (
<>
<X className="w-4 h-4 inline mr-1" />
Invalid
</>
)}
</span>
</div>
{validationResult.valid ? (
<div className="bg-green-50 rounded-lg p-4">
<p className="text-green-700 mb-3">
✅ Data successfully validates against the {validationResult.schema_used} schema!
</p>
<div className="bg-white rounded p-3">
<pre className="text-sm text-gray-800 overflow-x-auto">
{JSON.stringify(validationResult.data, null, 2)}
</pre>
</div>
</div>
) : (
<div className="bg-red-50 rounded-lg p-4">
<p className="text-red-700 mb-3">
❌ Data does not match the {validationResult.schema_used} schema.
</p>
<div className="space-y-2">
{validationResult.errors.map((error, index) => (
<div key={index} className="bg-white rounded p-3">
<div className="flex items-start space-x-2">
<X className="w-4 h-4 text-red-500 mt-0.5" />
<div>
<p className="font-medium text-red-800">
{error.path || 'Root'}
</p>
<p className="text-sm text-red-700">{error.message}</p>
{error.received && (
<p className="text-xs text-red-600 mt-1">
Received: {JSON.stringify(error.received)}
</p>
)}
</div>
</div>
</div>
))}
</div>
</div>
)}
</div>
)}
</div>
)}
</div>
</div>
</div>
</div>
);
}
export default StructuredOutput;

Step 3B: Adding Structured Output to Navigation

Section titled “Step 3B: Adding Structured Output to Navigation”

Update your src/App.jsx to include the new structured output component:

import { useState } from "react";
import StreamingChat from "./StreamingChat";
import ImageGenerator from "./ImageGenerator";
import AudioTranscription from "./AudioTranscription";
import FileAnalysis from "./FileAnalysis";
import TextToSpeech from "./TextToSpeech";
import VisionAnalysis from "./VisionAnalysis";
import VoiceInteraction from "./VoiceInteraction";
import MCPIntegration from "./MCPIntegration";
import StructuredOutput from "./StructuredOutput";
import { MessageSquare, Image, Mic, Folder, Volume2, Eye, Phone, Link, FileText } from "lucide-react";
function App() {
// 🧠 STATE: Navigation management
const [currentView, setCurrentView] = useState("chat"); // 'chat', 'images', 'audio', 'files', 'speech', 'vision', 'voice', 'mcp', or 'structured'
// 🎨 UI: Main app with navigation
return (
<div className="min-h-screen bg-gray-100">
{/* Navigation Header */}
<nav className="bg-white shadow-sm border-b border-gray-200">
<div className="max-w-7xl mx-auto px-4">
<div className="flex items-center justify-between h-16">
{/* Logo */}
<div className="flex items-center space-x-3">
<div className="w-8 h-8 bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg flex items-center justify-center">
<span className="text-white font-bold text-sm">AI</span>
</div>
<h1 className="text-xl font-bold text-gray-900">OpenAI Mastery</h1>
</div>
{/* Navigation Buttons */}
<div className="flex space-x-1 overflow-x-auto">
<button
onClick={() => setCurrentView("chat")}
className={`px-3 py-2 rounded-lg flex items-center space-x-2 transition-all duration-200 whitespace-nowrap ${
currentView === "chat"
? "bg-blue-100 text-blue-700 shadow-sm"
: "text-gray-600 hover:text-gray-900 hover:bg-gray-100"
}`}
>
<MessageSquare className="w-4 h-4" />
<span>Chat</span>
</button>
<button
onClick={() => setCurrentView("images")}
className={`px-3 py-2 rounded-lg flex items-center space-x-2 transition-all duration-200 whitespace-nowrap ${
currentView === "images"
? "bg-purple-100 text-purple-700 shadow-sm"
: "text-gray-600 hover:text-gray-900 hover:bg-gray-100"
}`}
>
<Image className="w-4 h-4" />
<span>Images</span>
</button>
<button
onClick={() => setCurrentView("audio")}
className={`px-3 py-2 rounded-lg flex items-center space-x-2 transition-all duration-200 whitespace-nowrap ${
currentView === "audio"
? "bg-blue-100 text-blue-700 shadow-sm"
: "text-gray-600 hover:text-gray-900 hover:bg-gray-100"
}`}
>
<Mic className="w-4 h-4" />
<span>Audio</span>
</button>
<button
onClick={() => setCurrentView("files")}
className={`px-3 py-2 rounded-lg flex items-center space-x-2 transition-all duration-200 whitespace-nowrap ${
currentView === "files"
? "bg-green-100 text-green-700 shadow-sm"
: "text-gray-600 hover:text-gray-900 hover:bg-gray-100"
}`}
>
<Folder className="w-4 h-4" />
<span>Files</span>
</button>
<button
onClick={() => setCurrentView("speech")}
className={`px-3 py-2 rounded-lg flex items-center space-x-2 transition-all duration-200 whitespace-nowrap ${
currentView === "speech"
? "bg-orange-100 text-orange-700 shadow-sm"
: "text-gray-600 hover:text-gray-900 hover:bg-gray-100"
}`}
>
<Volume2 className="w-4 h-4" />
<span>Speech</span>
</button>
<button
onClick={() => setCurrentView("vision")}
className={`px-3 py-2 rounded-lg flex items-center space-x-2 transition-all duration-200 whitespace-nowrap ${
currentView === "vision"
? "bg-indigo-100 text-indigo-700 shadow-sm"
: "text-gray-600 hover:text-gray-900 hover:bg-gray-100"
}`}
>
<Eye className="w-4 h-4" />
<span>Vision</span>
</button>
<button
onClick={() => setCurrentView("voice")}
className={`px-3 py-2 rounded-lg flex items-center space-x-2 transition-all duration-200 whitespace-nowrap ${
currentView === "voice"
? "bg-blue-100 text-blue-700 shadow-sm"
: "text-gray-600 hover:text-gray-900 hover:bg-gray-100"
}`}
>
<Phone className="w-4 h-4" />
<span>Voice</span>
</button>
<button
onClick={() => setCurrentView("mcp")}
className={`px-3 py-2 rounded-lg flex items-center space-x-2 transition-all duration-200 whitespace-nowrap ${
currentView === "mcp"
? "bg-green-100 text-green-700 shadow-sm"
: "text-gray-600 hover:text-gray-900 hover:bg-gray-100"
}`}
>
<Link className="w-4 h-4" />
<span>MCP</span>
</button>
<button
onClick={() => setCurrentView("structured")}
className={`px-3 py-2 rounded-lg flex items-center space-x-2 transition-all duration-200 whitespace-nowrap ${
currentView === "structured"
? "bg-purple-100 text-purple-700 shadow-sm"
: "text-gray-600 hover:text-gray-900 hover:bg-gray-100"
}`}
>
<FileText className="w-4 h-4" />
<span>Structured</span>
</button>
</div>
</div>
</div>
</nav>
{/* Main Content */}
<main className="h-[calc(100vh-4rem)]">
{currentView === "chat" && <StreamingChat />}
{currentView === "images" && <ImageGenerator />}
{currentView === "audio" && <AudioTranscription />}
{currentView === "files" && <FileAnalysis />}
{currentView === "speech" && <TextToSpeech />}
{currentView === "vision" && <VisionAnalysis />}
{currentView === "voice" && <VoiceInteraction />}
{currentView === "mcp" && <MCPIntegration />}
{currentView === "structured" && <StructuredOutput />}
</main>
</div>
);
}
export default App;

Let’s test your structured output feature step by step to make sure everything works correctly.

First, verify your backend routes work by testing them individually:

Test schema listing:

Terminal window
# Test the schemas endpoint
curl http://localhost:8000/api/structured/schemas

Test data generation:

Terminal window
# Test generating structured data
curl -X POST http://localhost:8000/api/structured/generate \
-H "Content-Type: application/json" \
-d '{"prompt": "Create a person named John Doe", "schema_name": "person"}'

Start both servers:

Backend (in your backend folder):

Terminal window
npm run dev

Frontend (in your frontend folder):

Terminal window
npm run dev

Test the complete flow:

  1. Navigate to Structured → Click the “Structured” tab in navigation
  2. Select schema → Choose from available Zod schemas
  3. Generate data → Write prompts and generate structured JSON
  4. Validate data → Test existing JSON against schemas
  5. Download results → Save generated data as JSON files
  6. Test different schemas → Try person, event, product, task, and analysis schemas
  7. Adjust settings → Change retry counts and temperature for different results

Test error scenarios:

❌ Invalid prompt: Try empty or invalid generation prompts
❌ Malformed JSON: Test validation with invalid JSON data
❌ Schema mismatch: Validate data against wrong schema
❌ Generation failure: Test with very complex or impossible prompts

Expected behavior:

  • Clear error messages with validation details
  • Retry logic for generation failures
  • Detailed schema validation error reporting
  • User can fix issues and retry

Congratulations! You’ve completed Module 2 with all advanced OpenAI features:

  • Extended your backend with Zod schema validation and structured output generation
  • Added React structured output component following the same patterns as your other features
  • Implemented guaranteed data formats with schema validation and retry logic
  • Created comprehensive validation with detailed error reporting and type safety
  • Added multiple schemas covering common use cases like persons, events, products, tasks, and analysis
  • Maintained consistent design with your existing application

Your complete Module 2 application now has:

  • Text chat with streaming responses
  • Image generation with DALL-E 3 and GPT-Image-1
  • Audio transcription with Whisper voice recognition
  • File analysis with intelligent document processing
  • Text-to-speech with natural voice synthesis
  • Vision analysis with GPT-4o visual intelligence
  • Voice interaction with GPT-4o Audio natural conversations
  • Function calling with external tools and APIs
  • Web search with real-time internet access
  • MCP integration with external data sources and services
  • Structured output with Zod validation and guaranteed data formats
  • Unified navigation between all features
  • Professional UI with consistent TailwindCSS styling

Ready for Module 3: You now have a comprehensive OpenAI application with all major features. Module 3 will focus on production readiness, safety, content moderation, performance optimization, and deployment strategies.

Your OpenAI mastery application now guarantees reliable data formats! 📋