Skip to content

[Solution]: Developing an AI-Powered Full-Stack App Builder #157

@kstij1

Description

@kstij1

AI Full-Stack App Builder – Complete Build and Integration Guide

Part 1: Building the Standalone AI Full-Stack App Builder

What You're Building

The AI Full-Stack App Builder is a web application that transforms natural language descriptions into complete, runnable web applications. Users describe what they want to build (e.g., "A project management app with users, projects, and tasks"), and the system generates database schemas, REST APIs, and frontend pages. The app includes a visual schema editor, code preview, and export functionality for generated projects.

Core Features

  • Natural Language Processing: Convert plain English descriptions into technical specifications
  • Database Schema Generation: Auto-create MongoDB schemas with proper relationships
  • API Generation: Generate complete REST APIs with CRUD operations
  • Frontend Scaffolding: Create Next.js pages and components
  • Visual Schema Editor: Interactive database schema editing and validation
  • Code Preview: Live preview of generated code before export
  • Project Export: Download complete projects as ZIP files or push to Git

Tech Stack

Frontend:

  • Next.js 14 with App Router
  • TypeScript for type safety
  • Tailwind CSS for styling
  • React Flow for schema visualization
  • Monaco Editor for code preview
  • React Hook Form for form management

Backend:

  • Next.js API routes
  • OpenAI GPT-4 for specification parsing
  • MongoDB for project storage
  • JSZip for project packaging
  • Git integration for repository creation

Development Tools:

  • ESLint and Prettier for code quality
  • Jest for testing
  • Docker for containerization

Project Structure

app-builder/
├── app/
│   ├── (auth)/
│   │   └── login/
│   ├── (dashboard)/
│   │   ├── builder/
│   │   │   ├── page.tsx
│   │   │   ├── new/
│   │   │   │   └── page.tsx
│   │   │   └── [id]/
│   │   │       └── page.tsx
│   │   └── layout.tsx
│   ├── api/
│   │   ├── projects/
│   │   │   ├── route.ts
│   │   │   ├── [id]/
│   │   │   │   └── route.ts
│   │   │   └── generate/
│   │   │       └── route.ts
│   │   ├── export/
│   │   │   └── route.ts
│   │   └── templates/
│   │       └── route.ts
│   ├── globals.css
│   └── layout.tsx
├── components/
│   ├── builder/
│   │   ├── SpecInput.tsx
│   │   ├── SchemaEditor.tsx
│   │   ├── APIPreview.tsx
│   │   ├── CodePreview.tsx
│   │   ├── ExportOptions.tsx
│   │   └── ProjectWizard.tsx
│   ├── ui/
│   │   ├── Button.tsx
│   │   ├── Input.tsx
│   │   ├── Textarea.tsx
│   │   ├── Modal.tsx
│   │   └── CodeEditor.tsx
│   └── layout/
│       ├── Header.tsx
│       └── Sidebar.tsx
├── lib/
│   ├── ai/
│   │   └── specParser.ts
│   ├── generator/
│   │   ├── schemaGenerator.ts
│   │   ├── apiGenerator.ts
│   │   ├── frontendGenerator.ts
│   │   └── projectBuilder.ts
│   ├── export/
│   │   ├── zipExporter.ts
│   │   └── gitExporter.ts
│   ├── database/
│   │   └── mongodb.ts
│   └── utils.ts
├── templates/
│   ├── backend/
│   │   ├── models/
│   │   ├── controllers/
│   │   ├── routes/
│   │   └── server.js
│   ├── frontend/
│   │   ├── pages/
│   │   ├── components/
│   │   └── styles/
│   └── config/
│       ├── package.json
│       └── README.md
├── types/
│   └── builder.ts
├── public/
│   └── icons/
├── package.json
├── next.config.js
├── tailwind.config.js
├── tsconfig.json
└── .env.local

Data Models

// types/builder.ts
export interface Entity {
  name: string;
  fields: Field[];
  relationships: Relationship[];
  indexes?: Index[];
}

export interface Field {
  name: string;
  type: 'string' | 'number' | 'boolean' | 'date' | 'array' | 'object';
  required: boolean;
  unique?: boolean;
  default?: any;
  validation?: ValidationRule[];
}

export interface Relationship {
  type: 'one-to-one' | 'one-to-many' | 'many-to-many';
  target: string;
  field?: string;
  foreignField?: string;
}

export interface Index {
  fields: string[];
  unique?: boolean;
  sparse?: boolean;
}

export interface ProjectSpec {
  id: string;
  name: string;
  description: string;
  entities: Entity[];
  techStack: {
    backend: 'node' | 'python' | 'java';
    frontend: 'nextjs' | 'react' | 'vue';
    database: 'mongodb' | 'postgresql' | 'mysql';
  };
  features: string[];
  createdAt: Date;
  updatedAt: Date;
  userId: string;
}

export interface GenerationRequest {
  description: string;
  techStack?: {
    backend?: string;
    frontend?: string;
    database?: string;
  };
  features?: string[];
}

API Endpoints

// app/api/projects/route.ts
POST   /api/projects           // Create new project spec
GET    /api/projects           // List user's projects

// app/api/projects/[id]/route.ts
GET    /api/projects/[id]      // Get specific project
PUT    /api/projects/[id]      // Update project
DELETE /api/projects/[id]      // Delete project

// app/api/projects/generate/route.ts
POST   /api/projects/generate  // Generate project from spec

// app/api/export/route.ts
POST   /api/export             // Export project as ZIP or Git

// app/api/templates/route.ts
GET    /api/templates          // Get available templates

Core Components

1. Specification Input Component

// components/builder/SpecInput.tsx
'use client';

import { useState } from 'react';
import { Button } from '@/components/ui/Button';
import { Textarea } from '@/components/ui/Textarea';

interface SpecInputProps {
  onSubmit: (spec: GenerationRequest) => void;
  loading?: boolean;
}

export default function SpecInput({ onSubmit, loading }: SpecInputProps) {
  const [description, setDescription] = useState('');
  const [techStack, setTechStack] = useState({
    backend: 'node',
    frontend: 'nextjs',
    database: 'mongodb'
  });
  const [features, setFeatures] = useState<string[]>([]);

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
    if (!description.trim()) return;
    
    onSubmit({
      description,
      techStack,
      features
    });
  };

  const addFeature = (feature: string) => {
    if (feature.trim() && !features.includes(feature)) {
      setFeatures([...features, feature]);
    }
  };

  const removeFeature = (index: number) => {
    setFeatures(features.filter((_, i) => i !== index));
  };

  return (
    <form onSubmit={handleSubmit} className="space-y-6">
      <div>
        <label className="block text-sm font-medium mb-2">
          Describe your application
        </label>
        <Textarea
          value={description}
          onChange={(e) => setDescription(e.target.value)}
          placeholder="Describe what you want to build... (e.g., 'A project management app with users, projects, tasks, and comments. Users can create projects, assign tasks to team members, and track progress.')"
          rows={6}
          className="w-full"
        />
      </div>
      
      <div className="grid grid-cols-3 gap-4">
        <div>
          <label className="block text-sm font-medium mb-2">Backend</label>
          <select
            value={techStack.backend}
            onChange={(e) => setTechStack({...techStack, backend: e.target.value})}
            className="w-full p-2 border rounded-md"
          >
            <option value="node">Node.js</option>
            <option value="python">Python</option>
            <option value="java">Java</option>
          </select>
        </div>
        
        <div>
          <label className="block text-sm font-medium mb-2">Frontend</label>
          <select
            value={techStack.frontend}
            onChange={(e) => setTechStack({...techStack, frontend: e.target.value})}
            className="w-full p-2 border rounded-md"
          >
            <option value="nextjs">Next.js</option>
            <option value="react">React</option>
            <option value="vue">Vue.js</option>
          </select>
        </div>
        
        <div>
          <label className="block text-sm font-medium mb-2">Database</label>
          <select
            value={techStack.database}
            onChange={(e) => setTechStack({...techStack, database: e.target.value})}
            className="w-full p-2 border rounded-md"
          >
            <option value="mongodb">MongoDB</option>
            <option value="postgresql">PostgreSQL</option>
            <option value="mysql">MySQL</option>
          </select>
        </div>
      </div>
      
      <div>
        <label className="block text-sm font-medium mb-2">Additional Features</label>
        <div className="flex flex-wrap gap-2 mb-2">
          {features.map((feature, index) => (
            <span
              key={index}
              className="bg-blue-100 text-blue-800 px-2 py-1 rounded text-sm flex items-center gap-1"
            >
              {feature}
              <button
                type="button"
                onClick={() => removeFeature(index)}
                className="text-blue-600 hover:text-blue-800"
              >
                ×
              </button>
            </span>
          ))}
        </div>
        <input
          type="text"
          placeholder="Add feature (e.g., 'authentication', 'file upload')"
          className="w-full p-2 border rounded-md"
          onKeyPress={(e) => {
            if (e.key === 'Enter') {
              e.preventDefault();
              addFeature(e.currentTarget.value);
              e.currentTarget.value = '';
            }
          }}
        />
      </div>
      
      <Button type="submit" disabled={loading || !description.trim()}>
        {loading ? 'Generating...' : 'Generate App'}
      </Button>
    </form>
  );
}

2. Schema Editor Component

// components/builder/SchemaEditor.tsx
'use client';

import { useState } from 'react';
import { Entity } from '@/types/builder';
import { Button } from '@/components/ui/Button';

interface SchemaEditorProps {
  entities: Entity[];
  onUpdateEntity: (index: number, entity: Entity) => void;
  onAddEntity: () => void;
  onRemoveEntity: (index: number) => void;
}

export default function SchemaEditor({ 
  entities, 
  onUpdateEntity, 
  onAddEntity, 
  onRemoveEntity 
}: SchemaEditorProps) {
  const [selectedEntity, setSelectedEntity] = useState<number>(0);

  const updateEntityField = (entityIndex: number, fieldIndex: number, updates: Partial<Field>) => {
    const entity = entities[entityIndex];
    const newFields = [...entity.fields];
    newFields[fieldIndex] = { ...newFields[fieldIndex], ...updates };
    onUpdateEntity(entityIndex, { ...entity, fields: newFields });
  };

  const addField = (entityIndex: number) => {
    const entity = entities[entityIndex];
    const newField: Field = {
      name: 'newField',
      type: 'string',
      required: false,
      unique: false
    };
    onUpdateEntity(entityIndex, { ...entity, fields: [...entity.fields, newField] });
  };

  const removeField = (entityIndex: number, fieldIndex: number) => {
    const entity = entities[entityIndex];
    const newFields = entity.fields.filter((_, i) => i !== fieldIndex);
    onUpdateEntity(entityIndex, { ...entity, fields: newFields });
  };

  return (
    <div className="flex h-full">
      {/* Entity List */}
      <div className="w-64 bg-gray-50 border-r p-4">
        <div className="flex items-center justify-between mb-4">
          <h3 className="font-semibold">Entities</h3>
          <Button onClick={onAddEntity} size="sm">
            + Add
          </Button>
        </div>
        
        <div className="space-y-2">
          {entities.map((entity, index) => (
            <div
              key={index}
              className={`p-3 border rounded cursor-pointer ${
                selectedEntity === index ? 'bg-blue-50 border-blue-300' : 'bg-white'
              }`}
              onClick={() => setSelectedEntity(index)}
            >
              <div className="flex items-center justify-between">
                <span className="font-medium">{entity.name}</span>
                <button
                  onClick={(e) => {
                    e.stopPropagation();
                    onRemoveEntity(index);
                  }}
                  className="text-red-500 hover:text-red-700"
                >
                  ×
                </button>
              </div>
              <div className="text-sm text-gray-500">
                {entity.fields.length} fields
              </div>
            </div>
          ))}
        </div>
      </div>
      
      {/* Entity Editor */}
      <div className="flex-1 p-4">
        {entities[selectedEntity] && (
          <div>
            <div className="mb-4">
              <input
                type="text"
                value={entities[selectedEntity].name}
                onChange={(e) => onUpdateEntity(selectedEntity, {
                  ...entities[selectedEntity],
                  name: e.target.value
                })}
                className="text-xl font-bold border-none outline-none"
              />
            </div>
            
            <div className="space-y-3">
              <div className="flex items-center justify-between">
                <h4 className="font-medium">Fields</h4>
                <Button onClick={() => addField(selectedEntity)} size="sm">
                  + Add Field
                </Button>
              </div>
              
              {entities[selectedEntity].fields.map((field, fieldIndex) => (
                <div key={fieldIndex} className="flex items-center space-x-2 p-3 border rounded">
                  <input
                    type="text"
                    value={field.name}
                    onChange={(e) => updateEntityField(selectedEntity, fieldIndex, {
                      name: e.target.value
                    })}
                    className="flex-1 p-2 border rounded"
                    placeholder="Field name"
                  />
                  
                  <select
                    value={field.type}
                    onChange={(e) => updateEntityField(selectedEntity, fieldIndex, {
                      type: e.target.value as any
                    })}
                    className="p-2 border rounded"
                  >
                    <option value="string">String</option>
                    <option value="number">Number</option>
                    <option value="boolean">Boolean</option>
                    <option value="date">Date</option>
                    <option value="array">Array</option>
                    <option value="object">Object</option>
                  </select>
                  
                  <label className="flex items-center space-x-1">
                    <input
                      type="checkbox"
                      checked={field.required}
                      onChange={(e) => updateEntityField(selectedEntity, fieldIndex, {
                        required: e.target.checked
                      })}
                    />
                    <span className="text-sm">Required</span>
                  </label>
                  
                  <label className="flex items-center space-x-1">
                    <input
                      type="checkbox"
                      checked={field.unique || false}
                      onChange={(e) => updateEntityField(selectedEntity, fieldIndex, {
                        unique: e.target.checked
                      })}
                    />
                    <span className="text-sm">Unique</span>
                  </label>
                  
                  <button
                    onClick={() => removeField(selectedEntity, fieldIndex)}
                    className="text-red-500 hover:text-red-700"
                  >
                    ×
                  </button>
                </div>
              ))}
            </div>
          </div>
        )}
      </div>
    </div>
  );
}

3. AI Specification Parser

// lib/ai/specParser.ts
import OpenAI from 'openai';

const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

export async function parseSpecification(request: GenerationRequest): Promise<Entity[]> {
  const systemPrompt = `You are an expert software architect. Parse the user's application description and generate a database schema.

Return a JSON array of entities with this exact structure:
[
  {
    "name": "EntityName",
    "fields": [
      {
        "name": "fieldName",
        "type": "string|number|boolean|date|array|object",
        "required": true|false,
        "unique": true|false
      }
    ],
    "relationships": [
      {
        "type": "one-to-one|one-to-many|many-to-many",
        "target": "TargetEntityName",
        "field": "foreignKeyField"
      }
    ]
  }
]

Guidelines:
- Use ${request.techStack?.database || 'mongodb'} database conventions
- Include common fields like id, createdAt, updatedAt
- Create proper relationships between entities
- Use appropriate field types and constraints
- Consider the features: ${request.features?.join(', ') || 'basic CRUD'}`;

  const response = await openai.chat.completions.create({
    model: 'gpt-4',
    messages: [
      { role: 'system', content: systemPrompt },
      { role: 'user', content: request.description }
    ],
    temperature: 0.3,
  });

  const content = response.choices[0].message.content;
  return JSON.parse(content || '[]');
}

4. Code Generators

// lib/generator/schemaGenerator.ts
export function generateMongoSchema(entity: Entity): string {
  const fields = entity.fields.map(field => {
    let fieldDef = `  ${field.name}: { type: ${getMongooseType(field.type)}`;
    if (field.required) fieldDef += ', required: true';
    if (field.unique) fieldDef += ', unique: true';
    if (field.default !== undefined) fieldDef += `, default: ${JSON.stringify(field.default)}`;
    fieldDef += ' }';
    return fieldDef;
  }).join(',\n');

  return `const mongoose = require('mongoose');

const ${entity.name}Schema = new mongoose.Schema({
${fields}
}, {
  timestamps: true
});

module.exports = mongoose.model('${entity.name}', ${entity.name}Schema);`;
}

function getMongooseType(type: string): string {
  const typeMap = {
    'string': 'String',
    'number': 'Number',
    'boolean': 'Boolean',
    'date': 'Date',
    'array': '[String]',
    'object': 'Object'
  };
  return typeMap[type] || 'String';
}

// lib/generator/apiGenerator.ts
export function generateController(entity: Entity): string {
  const name = entity.name.toLowerCase();
  const Name = entity.name;
  
  return `const ${Name} = require('../models/${Name}');

exports.getAll = async (req, res) => {
  try {
    const ${name}s = await ${Name}.find();
    res.json(${name}s);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
};

exports.getById = async (req, res) => {
  try {
    const ${name} = await ${Name}.findById(req.params.id);
    if (!${name}) return res.status(404).json({ error: 'Not found' });
    res.json(${name});
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
};

exports.create = async (req, res) => {
  try {
    const ${name} = new ${Name}(req.body);
    await ${name}.save();
    res.status(201).json(${name});
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
};

exports.update = async (req, res) => {
  try {
    const ${name} = await ${Name}.findByIdAndUpdate(
      req.params.id, 
      req.body, 
      { new: true }
    );
    if (!${name}) return res.status(404).json({ error: 'Not found' });
    res.json(${name});
  } catch (error) {
    res.status(400).json({ error: error.message });
  }
};

exports.delete = async (req, res) => {
  try {
    const ${name} = await ${Name}.findByIdAndDelete(req.params.id);
    if (!${name}) return res.status(404).json({ error: 'Not found' });
    res.json({ message: 'Deleted successfully' });
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
};`;
}

// lib/generator/frontendGenerator.ts
export function generateNextJSPage(entity: Entity): string {
  const name = entity.name.toLowerCase();
  const Name = entity.name;
  
  return `import { useState, useEffect } from 'react';
import { ${Name} } from '@/types/${name}';

export default function ${Name}Page() {
  const [${name}s, set${Name}s] = useState<${Name}[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch${Name}s();
  }, []);

  const fetch${Name}s = async () => {
    try {
      const response = await fetch('/api/${name}s');
      const data = await response.json();
      set${Name}s(data);
    } catch (error) {
      console.error('Error fetching ${name}s:', error);
    } finally {
      setLoading(false);
    }
  };

  if (loading) return <div>Loading...</div>;

  return (
    <div className="container mx-auto p-6">
      <h1 className="text-2xl font-bold mb-6">${Name}s</h1>
      
      <div className="grid gap-4">
        {${name}s.map((${name}) => (
          <div key={${name}.id} className="border rounded p-4">
            <h3 className="font-semibold">${name}.name</h3>
            {/* Add more fields as needed */}
          </div>
        ))}
      </div>
    </div>
  );
}`;
}

5. Project Builder and Exporter

// lib/generator/projectBuilder.ts
import JSZip from 'jszip';
import { ProjectSpec } from '@/types/builder';
import { generateMongoSchema } from './schemaGenerator';
import { generateController } from './apiGenerator';
import { generateNextJSPage } from './frontendGenerator';

export async function buildProject(spec: ProjectSpec): Promise<Buffer> {
  const zip = new JSZip();
  
  // Add package.json
  zip.file('package.json', generatePackageJson(spec));
  
  // Add backend files
  const backendFolder = zip.folder('backend');
  backendFolder.file('server.js', generateServerFile(spec));
  
  const modelsFolder = backendFolder.folder('models');
  spec.entities.forEach(entity => {
    modelsFolder.file(`${entity.name}.js`, generateMongoSchema(entity));
  });
  
  const controllersFolder = backendFolder.folder('controllers');
  spec.entities.forEach(entity => {
    controllersFolder.file(`${entity.name}Controller.js`, generateController(entity));
  });
  
  // Add frontend files
  const frontendFolder = zip.folder('frontend');
  frontendFolder.file('package.json', generateFrontendPackageJson(spec));
  
  const pagesFolder = frontendFolder.folder('pages');
  spec.entities.forEach(entity => {
    pagesFolder.file(`${entity.name.toLowerCase()}.tsx`, generateNextJSPage(entity));
  });
  
  // Add README
  zip.file('README.md', generateReadme(spec));
  
  return zip.generateAsync({ type: 'nodebuffer' });
}

function generatePackageJson(spec: ProjectSpec): string {
  return JSON.stringify({
    name: spec.name.toLowerCase().replace(/\s+/g, '-'),
    version: '1.0.0',
    description: spec.description,
    main: 'backend/server.js',
    scripts: {
      start: 'node backend/server.js',
      dev: 'nodemon backend/server.js',
      'dev:frontend': 'cd frontend && npm run dev'
    },
    dependencies: {
      express: '^4.18.0',
      mongoose: '^7.0.0',
      cors: '^2.8.5',
      dotenv: '^16.0.0'
    },
    devDependencies: {
      nodemon: '^2.0.0'
    }
  }, null, 2);
}

function generateServerFile(spec: ProjectSpec): string {
  const imports = spec.entities.map(entity => 
    `const ${entity.name}Controller = require('./controllers/${entity.name}Controller');`
  ).join('\n');
  
  const routes = spec.entities.map(entity => {
    const name = entity.name.toLowerCase();
    return `
// ${entity.name} routes
app.get('/api/${name}s', ${entity.name}Controller.getAll);
app.get('/api/${name}s/:id', ${entity.name}Controller.getById);
app.post('/api/${name}s', ${entity.name}Controller.create);
app.put('/api/${name}s/:id', ${entity.name}Controller.update);
app.delete('/api/${name}s/:id', ${entity.name}Controller.delete);`;
  }).join('\n');
  
  return `const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
require('dotenv').config();

${imports}

const app = express();
const PORT = process.env.PORT || 3000;

// Middleware
app.use(cors());
app.use(express.json());

// Connect to MongoDB
mongoose.connect(process.env.MONGODB_URI || 'mongodb://localhost:27017/${spec.name.toLowerCase()}');

// Routes
${routes}

app.listen(PORT, () => {
  console.log(\`Server running on port \${PORT}\`);
});`;
}

Main Application Page

// app/(dashboard)/builder/page.tsx
'use client';

import { useState, useEffect } from 'react';
import { ProjectSpec, Entity } from '@/types/builder';
import SpecInput from '@/components/builder/SpecInput';
import SchemaEditor from '@/components/builder/SchemaEditor';
import APIPreview from '@/components/builder/APIPreview';
import CodePreview from '@/components/builder/CodePreview';
import ExportOptions from '@/components/builder/ExportOptions';

export default function BuilderPage() {
  const [projects, setProjects] = useState<ProjectSpec[]>([]);
  const [currentProject, setCurrentProject] = useState<ProjectSpec | null>(null);
  const [loading, setLoading] = useState(false);
  const [step, setStep] = useState<'input' | 'schema' | 'preview' | 'export'>('input');

  const handleGenerateSpec = async (request: GenerationRequest) => {
    setLoading(true);
    try {
      const response = await fetch('/api/projects/generate', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(request),
      });
      
      const entities = await response.json();
      const newProject: ProjectSpec = {
        id: crypto.randomUUID(),
        name: 'New Project',
        description: request.description,
        entities,
        techStack: request.techStack || {
          backend: 'node',
          frontend: 'nextjs',
          database: 'mongodb'
        },
        features: request.features || [],
        createdAt: new Date(),
        updatedAt: new Date(),
        userId: 'current-user-id'
      };
      
      setCurrentProject(newProject);
      setStep('schema');
    } catch (error) {
      console.error('Failed to generate spec:', error);
    } finally {
      setLoading(false);
    }
  };

  const handleUpdateEntity = (index: number, entity: Entity) => {
    if (!currentProject) return;
    
    const updatedEntities = [...currentProject.entities];
    updatedEntities[index] = entity;
    
    setCurrentProject({
      ...currentProject,
      entities: updatedEntities,
      updatedAt: new Date()
    });
  };

  const handleAddEntity = () => {
    if (!currentProject) return;
    
    const newEntity: Entity = {
      name: 'NewEntity',
      fields: [
        { name: 'id', type: 'string', required: true, unique: true },
        { name: 'name', type: 'string', required: true }
      ],
      relationships: []
    };
    
    setCurrentProject({
      ...currentProject,
      entities: [...currentProject.entities, newEntity],
      updatedAt: new Date()
    });
  };

  const handleRemoveEntity = (index: number) => {
    if (!currentProject) return;
    
    const updatedEntities = currentProject.entities.filter((_, i) => i !== index);
    setCurrentProject({
      ...currentProject,
      entities: updatedEntities,
      updatedAt: new Date()
    });
  };

  const handleExport = async (format: 'zip' | 'git') => {
    if (!currentProject) return;
    
    try {
      const response = await fetch('/api/export', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ projectId: currentProject.id, format }),
      });
      
      if (format === 'zip') {
        const blob = await response.blob();
        const url = URL.createObjectURL(blob);
        const a = document.createElement('a');
        a.href = url;
        a.download = `${currentProject.name}.zip`;
        a.click();
        URL.revokeObjectURL(url);
      }
    } catch (error) {
      console.error('Failed to export project:', error);
    }
  };

  return (
    <div className="min-h-screen bg-gray-50">
      <div className="max-w-7xl mx-auto p-6">
        <div className="mb-8">
          <h1 className="text-3xl font-bold mb-2">AI Full-Stack App Builder</h1>
          <p className="text-gray-600">Transform your ideas into complete web applications</p>
        </div>
        
        {/* Progress Steps */}
        <div className="flex items-center space-x-4 mb-8">
          {['input', 'schema', 'preview', 'export'].map((stepName, index) => (
            <div key={stepName} className="flex items-center">
              <div className={`w-8 h-8 rounded-full flex items-center justify-center ${
                step === stepName ? 'bg-blue-600 text-white' : 'bg-gray-200 text-gray-600'
              }`}>
                {index + 1}
              </div>
              <span className={`ml-2 capitalize ${
                step === stepName ? 'text-blue-600 font-medium' : 'text-gray-600'
              }`}>
                {stepName}
              </span>
              {index < 3 && <div className="w-8 h-0.5 bg-gray-200 mx-2" />}
            </div>
          ))}
        </div>
        
        {/* Step Content */}
        {step === 'input' && (
          <div className="max-w-2xl mx-auto">
            <SpecInput onSubmit={handleGenerateSpec} loading={loading} />
          </div>
        )}
        
        {step === 'schema' && currentProject && (
          <div className="bg-white rounded-lg shadow-sm h-96">
            <SchemaEditor
              entities={currentProject.entities}
              onUpdateEntity={handleUpdateEntity}
              onAddEntity={handleAddEntity}
              onRemoveEntity={handleRemoveEntity}
            />
            <div className="p-4 border-t">
              <button
                onClick={() => setStep('preview')}
                className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700"
              >
                Continue to Preview
              </button>
            </div>
          </div>
        )}
        
        {step === 'preview' && currentProject && (
          <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
            <div className="bg-white rounded-lg shadow-sm p-6">
              <h3 className="text-lg font-semibold mb-4">API Endpoints</h3>
              <APIPreview entities={currentProject.entities} />
            </div>
            <div className="bg-white rounded-lg shadow-sm p-6">
              <h3 className="text-lg font-semibold mb-4">Generated Code</h3>
              <CodePreview project={currentProject} />
            </div>
          </div>
        )}
        
        {step === 'export' && currentProject && (
          <div className="max-w-2xl mx-auto">
            <ExportOptions
              project={currentProject}
              onExport={handleExport}
            />
          </div>
        )}
      </div>
    </div>
  );
}

Environment Configuration

# .env.local
MONGODB_URI=mongodb://localhost:27017/app_builder
OPENAI_API_KEY=your_openai_api_key_here
GITHUB_TOKEN=your_github_token_here
NEXTAUTH_SECRET=your_nextauth_secret_here
NEXTAUTH_URL=http://localhost:3000

Package Dependencies

{
  "dependencies": {
    "next": "14.0.0",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "typescript": "5.0.0",
    "tailwindcss": "3.3.0",
    "openai": "4.0.0",
    "jszip": "3.10.0",
    "mongodb": "6.0.0",
    "react-flow-renderer": "10.3.0",
    "@monaco-editor/react": "4.6.0",
    "react-hook-form": "7.45.0",
    "@hookform/resolvers": "3.3.0",
    "zod": "3.22.0"
  },
  "devDependencies": {
    "@types/node": "20.0.0",
    "@types/react": "18.2.0",
    "@types/react-dom": "18.2.0",
    "eslint": "8.50.0",
    "eslint-config-next": "14.0.0",
    "prettier": "3.0.0",
    "jest": "29.7.0",
    "@testing-library/react": "13.4.0"
  }
}

Part 2: Now that your app is working fine, let's integrate it with Weam

Integration Overview

Once your AI Full-Stack App Builder is fully functional as a standalone application, you can integrate it into the Weam ecosystem. This integration involves:

  1. Authentication: Using Weam's session management
  2. Database: Migrating to Weam's MongoDB with proper naming conventions
  3. UI Theming: Matching Weam's design system
  4. Routing: Setting up base path configuration
  5. Deployment: Configuring NGINX and Docker

Weam Integration Steps

Step 1: Authentication Integration

Replace your standalone authentication with Weam's iron-session:

// lib/session.ts
import { getIronSession } from 'iron-session';
import { cookies } from 'next/headers';

export interface SessionData {
  user?: {
    id: string;
    email: string;
    name: string;
  };
}

export const sessionOptions = {
  cookieName: 'weam_session',
  password: process.env.IRON_SESSION_PASSWORD!,
  cookieOptions: {
    secure: process.env.NODE_ENV === 'production',
    httpOnly: true,
    sameSite: 'lax' as const,
    path: '/',
    domain: process.env.COOKIE_DOMAIN,
    maxAge: 60 * 60 * 24 * 7, // 7 days
  },
};

export async function getSession() {
  const cookieStore = await cookies();
  return getIronSession<SessionData>(cookieStore, sessionOptions);
}

Step 2: Database Migration

Update your database collections to follow Weam's naming convention:

// lib/database/mongodb.ts
import { MongoClient } from 'mongodb';

const client = new MongoClient(process.env.MONGODB_URI!);
export const db = client.db('weam');

// Weam naming convention: solution_{solutionName}_{tableName}
export const projects = db.collection('solution_appbuilder_projects');
export const exports = db.collection('solution_appbuilder_exports');
export const templates = db.collection('solution_appbuilder_templates');

Step 3: Base Path Configuration

Update your Next.js configuration for Weam routing:

// next.config.js
const nextConfig = {
  basePath: '/app-builder',
  assetPrefix: '/app-builder',
  async rewrites() {
    return [
      {
        source: '/app-builder/api/:path*',
        destination: '/api/:path*',
      },
    ];
  },
};

module.exports = nextConfig;

Step 4: Environment Variables

Update your environment configuration:

# .env.local
NEXT_PUBLIC_API_BASE_PATH=/app-builder
MONGODB_URI=mongodb://localhost:27017/weam
IRON_SESSION_PASSWORD=your-secure-session-password
COOKIE_DOMAIN=.weam.ai
OPENAI_API_KEY=your-openai-key
GITHUB_TOKEN=your-github-token

Step 5: Sidebar Integration

Add your solution to Weam's sidebar:

// Update src/seeders/superSolution.json
{
  "name": "AI Full-Stack App Builder",
  "path": "/app-builder",
  "icon": "CodeBracket",
  "description": "Generate full-stack applications from natural language specs"
}

Step 6: NGINX Configuration

Add routing configuration to Weam's NGINX:

# Add to /etc/nginx/sites-available/weam
location /app-builder/ {
    proxy_pass http://localhost:3011/app-builder/;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header X-Forwarded-Proto $scheme;
}

Step 7: Docker Configuration

Create Docker setup for deployment:

# Dockerfile
FROM node:18-alpine AS builder

WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production

COPY . .
RUN npm run build

FROM node:18-alpine AS production
WORKDIR /app

COPY --from=builder /app/.next ./.next
COPY --from=builder /app/public ./public
COPY --from=builder /app/package*.json ./
COPY --from=builder /app/node_modules ./node_modules

EXPOSE 3011

CMD ["npm", "start"]
# docker-compose.yml
version: '3.8'

services:
  app-builder:
    build: .
    ports:
      - "3011:3011"
    environment:
      - NODE_ENV=production
      - NEXT_PUBLIC_API_BASE_PATH=/app-builder
      - MONGODB_URI=${MONGODB_URI}
      - IRON_SESSION_PASSWORD=${IRON_SESSION_PASSWORD}
      - COOKIE_DOMAIN=.weam.ai
      - OPENAI_API_KEY=${OPENAI_API_KEY}
      - GITHUB_TOKEN=${GITHUB_TOKEN}
    restart: unless-stopped

Final Integration Checklist

✅ Standalone app fully functional
✅ Weam authentication integrated
✅ Database collections follow naming convention
✅ Base path configuration set
✅ Sidebar entry added
✅ NGINX routing configured
✅ Docker deployment ready
✅ Environment variables updated

Testing Integration

  1. Authentication Test: Verify users can access the app through Weam login
  2. Database Test: Confirm data is stored with proper user/company scoping
  3. Routing Test: Check that all routes work under /app-builder base path
  4. Generation Test: Verify AI spec parsing works in production
  5. Export Test: Confirm ZIP and Git export functionality works
  6. UI Test: Ensure theming matches Weam's design system

Your AI Full-Stack App Builder is now fully integrated into the Weam ecosystem while maintaining all its standalone functionality!

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions