Back to Blog
β€’12 min read

Build a Complete CRUD Application with Symulate BYOK and Your OpenAI API Key

Learn how to create a fully functional CRUD application with realistic AI-generated data using Symulate SDK and your own OpenAI API key. No platform account needed, completely open source.

BYOKOpen SourceCRUDTutorialOpenAI

Build a Complete CRUD Application with Symulate BYOK and Your OpenAI API Key

Want to build a working CRUD application with realistic, AI-generated data without paying for yet another SaaS platform? Symulate SDK is now fully open source (MIT License), and you can use it completely free with just your own OpenAI API key.

In this tutorial, we'll build a complete e-commerce product management system with:

  • βœ… Full CRUD operations (Create, Read, Update, Delete)
  • βœ… AI-generated realistic product data
  • βœ… Local persistence (works offline after initial generation)
  • βœ… Flexible response schemas with aggregates
  • βœ… Type-safe TypeScript throughout
  • βœ… Total cost: ~$0.01-0.05 per development session

Why BYOK (Bring Your Own Key)?

Before we dive in, let's understand the benefits:

What you get:

  • Complete SDK functionality - no limitations
  • AI-realistic data generation
  • Local persistence (localStorage in browser, filesystem in Node.js)
  • Zero vendor lock-in
  • Open source MIT license

What you pay:

  • Only OpenAI API costs (~$0.001 per generation with gpt-4o-mini)
  • Essentially free for development and prototyping

What you don't get:

  • Cloud hosting (you use local persistence instead)
  • Branch isolation for multi-tenant demos
  • Team collaboration features

For individual developers, startups, and learning projects, BYOK is perfect.

Prerequisites

Before we start, you'll need:

  1. Node.js 18+ installed
  2. An OpenAI API key - Get one free at platform.openai.com/api-keys
  3. Basic TypeScript knowledge

Step 1: Project Setup

Let's create a new project and install Symulate SDK:

# Create new project
mkdir symulate-product-manager
cd symulate-product-manager
npm init -y

# Install Symulate SDK
npm install @symulate/sdk

# Install TypeScript and dev dependencies
npm install -D typescript @types/node tsx

# Initialize TypeScript
npx tsc --init

Create a .env file for your OpenAI API key:

# .env
OPENAI_API_KEY=sk-your-key-here

Important: Add .env to your .gitignore to keep your API key secure!

Step 2: Configure Symulate with BYOK

Create src/config.ts:

import { configureSymulate } from '@symulate/sdk';

// Configure Symulate with your OpenAI API key
configureSymulate({
  openaiApiKey: process.env.OPENAI_API_KEY,
  generateMode: 'ai', // Use AI generation
  environment: 'development',
  // persistence automatically defaults to "local" for BYOK!
  // Data will be saved to .symulate-product-manager.json
});

console.log('βœ… Symulate configured with BYOK mode');

That's it! No platform account needed. The SDK automatically uses local filesystem persistence when you're in BYOK mode.

Step 3: Define Your Product Schema

Create src/schema.ts:

import { m, type Infer } from '@symulate/sdk';

// Define a rich product schema
export const ProductSchema = m.object({
  id: m.uuid(),
  name: m.string(),
  description: m.string(),
  price: m.number({ min: 10, max: 2000 }),
  category: m.string(),
  brand: m.string(),
  inStock: m.boolean(),
  rating: m.number({ min: 1, max: 5 }),
  reviewCount: m.number({ min: 0, max: 1000 }),
  imageUrl: m.internet.url(),
  tags: m.array(m.string(), { min: 2, max: 5 }),
  sku: m.string(),
  weight: m.number({ min: 0.1, max: 50 }),
  dimensions: m.object({
    length: m.number(),
    width: m.number(),
    height: m.number(),
  }),
});

// Infer TypeScript type from schema
export type Product = Infer<typeof ProductSchema>;

The schema defines the structure of our product data. The AI will use this to generate realistic products.

Step 4: Create the Products Collection

Create src/products.ts:

import { defineCollection } from '@symulate/sdk';
import { ProductSchema, type Product } from './schema';

// Define collection with AI-powered seed data
export const products = defineCollection<Product>({
  name: 'products',
  basePath: '/api/products',
  schema: ProductSchema,
  seedCount: 25, // Generate 25 products initially
  seedInstruction: `Generate realistic e-commerce products with these characteristics:
    - Categories: Electronics, Clothing, Home & Garden, Sports, Books, Beauty
    - Mix of well-known brands (Apple, Nike, Samsung, etc.) and fictional brands
    - Prices should be realistic for each category
    - 80% products in stock, 20% out of stock
    - Ratings mostly between 3-5 stars with realistic distribution
    - Diverse product types within each category
    - Professional product descriptions
    - Realistic SKU patterns (e.g., ELEC-APL-001)
  `,
  operations: {
    // Enable all CRUD operations with custom response schemas
    list: {
      responseSchema: m.object({
        data: m.array(ProductSchema),
        pagination: m.object({
          page: m.collectionsMeta.page(),
          limit: m.collectionsMeta.limit(),
          total: m.collectionsMeta.total(),
          totalPages: m.collectionsMeta.totalPages(),
        }),
        stats: m.object({
          averagePrice: m.collectionsMeta.avg('price'),
          totalValue: m.collectionsMeta.sum('price'),
          minPrice: m.collectionsMeta.min('price'),
          maxPrice: m.collectionsMeta.max('price'),
          inStockCount: m.collectionsMeta.count('inStock', true),
          outOfStockCount: m.collectionsMeta.count('inStock', false),
        }),
      }),
      mock: {
        delay: 300, // Simulate 300ms network latency
      },
    },
    get: {
      mock: { delay: 200 },
    },
    create: {
      mock: { delay: 400 },
    },
    update: {
      mock: { delay: 350 },
    },
    delete: {
      mock: { delay: 250 },
    },
  },
});

Notice the responseSchema - this gives us a flexible response with pagination, the actual data, AND computed statistics (average price, stock counts, etc.). This is all calculated automatically!

Step 5: Build the Application

Now let's create a complete CRUD interface. Create src/index.ts:

import './config'; // Initialize configuration
import { products } from './products';
import type { Product } from './schema';

// Helper to display product info
function displayProduct(product: Product): void {
  console.log(`
πŸ“¦ ${product.name}
   Brand: ${product.brand}
   Category: ${product.category}
   Price: $${product.price.toFixed(2)}
   Rating: ${product.rating}⭐ (${product.reviewCount} reviews)
   Stock: ${product.inStock ? 'βœ… In Stock' : '❌ Out of Stock'}
   SKU: ${product.sku}
   Tags: ${product.tags.join(', ')}
  `);
}

async function main() {
  console.log('\nπŸš€ Starting Product Manager with Symulate BYOK\n');

  try {
    // 1. LIST ALL PRODUCTS with statistics
    console.log('πŸ“‹ Listing all products...\n');
    const listResult = await products.list({
      page: 1,
      limit: 5,
      sortBy: 'price',
      sortOrder: 'desc',
    });

    console.log(`Found ${listResult.pagination.total} products (showing first ${listResult.data.length}):`);
    listResult.data.forEach(displayProduct);

    console.log('\nπŸ“Š Store Statistics:');
    console.log(`   Average Price: $${listResult.stats.averagePrice.toFixed(2)}`);
    console.log(`   Price Range: $${listResult.stats.minPrice} - $${listResult.stats.maxPrice}`);
    console.log(`   Total Inventory Value: $${listResult.stats.totalValue.toFixed(2)}`);
    console.log(`   In Stock: ${listResult.stats.inStockCount} products`);
    console.log(`   Out of Stock: ${listResult.stats.outOfStockCount} products`);

    // 2. GET A SINGLE PRODUCT
    const firstProductId = listResult.data[0].id;
    console.log(`\nπŸ” Getting details for product: ${firstProductId}`);
    const singleProduct = await products.get(firstProductId);
    if (singleProduct) {
      displayProduct(singleProduct);
      console.log(`   Description: ${singleProduct.description}`);
      console.log(`   Dimensions: ${singleProduct.dimensions.length}x${singleProduct.dimensions.width}x${singleProduct.dimensions.height} cm`);
      console.log(`   Weight: ${singleProduct.weight} kg`);
    }

    // 3. CREATE A NEW PRODUCT
    console.log('\nβž• Creating a new product...');
    const newProduct = await products.create({
      name: 'Custom Gaming Laptop Pro',
      description: 'High-performance gaming laptop with RTX 4080, 32GB RAM, and 1TB NVMe SSD',
      price: 2499.99,
      category: 'Electronics',
      brand: 'TechPro Gaming',
      inStock: true,
      rating: 4.8,
      reviewCount: 0,
      imageUrl: 'https://example.com/gaming-laptop.jpg',
      tags: ['gaming', 'laptop', 'high-performance', 'RTX'],
      sku: 'ELEC-TPG-LAPTOP-001',
      weight: 2.5,
      dimensions: {
        length: 35,
        width: 25,
        height: 2.5,
      },
    });
    console.log('βœ… Product created successfully!');
    displayProduct(newProduct);

    // 4. UPDATE THE PRODUCT
    console.log('\n✏️  Updating product price (Black Friday sale!)...');
    const updated = await products.update(newProduct.id, {
      price: 1999.99,
      reviewCount: 15,
    });
    if (updated) {
      console.log('βœ… Product updated successfully!');
      console.log(`   New price: $${updated.price} (was $${newProduct.price})`);
      console.log(`   New review count: ${updated.reviewCount}`);
    }

    // 5. FILTER PRODUCTS
    console.log('\nπŸ”Ž Finding all Electronics under $500...');
    const filtered = await products.list({
      filter: {
        category: 'Electronics',
        price: { $lt: 500 },
        inStock: true,
      },
      limit: 3,
    });
    console.log(`Found ${filtered.data.length} matching products:`);
    filtered.data.forEach(displayProduct);

    // 6. DELETE THE PRODUCT
    console.log(`\nπŸ—‘οΈ  Deleting test product (${newProduct.id})...`);
    const deleted = await products.delete(newProduct.id);
    console.log(deleted ? 'βœ… Product deleted successfully!' : '❌ Failed to delete product');

    // 7. FINAL STATS
    console.log('\nπŸ“Š Final Statistics:');
    const finalList = await products.list({ limit: 1 });
    console.log(`   Total products: ${finalList.pagination.total}`);
    console.log(`   Average price: $${finalList.stats.averagePrice.toFixed(2)}`);
    console.log(`   In stock: ${finalList.stats.inStockCount}`);

    console.log('\nβœ… Demo completed successfully!');
    console.log('\nπŸ’Ύ Data persisted to: .symulate-product-manager.json');
    console.log('πŸ”„ Run this again to see data loaded from persistence!');
    console.log('πŸ’° OpenAI API cost for initial generation: ~$0.02');

  } catch (error: any) {
    console.error('❌ Error:', error.message);
    process.exit(1);
  }
}

// Run the application
main();

Step 6: Run Your Application

Add a script to package.json:

{
  "scripts": {
    "start": "tsx src/index.ts"
  }
}

Now run it:

npm start

On first run:

  • The SDK connects to OpenAI and generates 25 realistic products (~$0.02 cost)
  • Data is saved to .symulate-product-manager.json
  • You'll see all CRUD operations execute with simulated delays

On subsequent runs:

  • Data loads instantly from the local file (FREE!)
  • No OpenAI API calls unless you clear the file
  • Full offline development capability

What Just Happened?

Let's break down the magic:

1. AI-Generated Realistic Data

The seedInstruction told the AI exactly what kind of products to generate. Instead of getting "Product 1", "Product 2", you get realistic products like:

  • "Apple MacBook Pro 16-inch M3 Max"
  • "Nike Air Zoom Pegasus 40 Running Shoes"
  • "Samsung 55-inch QLED Smart TV"

Each with realistic prices, descriptions, ratings, and metadata.

2. Local Persistence (Automatic)

When using BYOK mode, Symulate automatically saves all data locally:

  • Browser: Uses localStorage
  • Node.js: Uses filesystem (JSON file)

This means after the initial AI generation, everything works offline and costs nothing.

3. Flexible Response Schemas

The responseSchema lets you customize the API response structure:

{
  data: [...products...],
  pagination: { page: 1, limit: 5, total: 25, totalPages: 5 },
  stats: {
    averagePrice: 245.67,
    totalValue: 6141.75,
    minPrice: 12.99,
    maxPrice: 1299.99,
    inStockCount: 20,
    outOfStockCount: 5
  }
}

All these statistics are calculated automatically from your data. No manual computation needed!

4. Type Safety Throughout

TypeScript knows the exact shape of your data:

const result = await products.list();
result.data[0].price // βœ… TypeScript knows this is a number
result.stats.averagePrice // βœ… TypeScript knows this exists
result.randomField // ❌ TypeScript error - field doesn't exist

5. Delay Simulation

The mock: { delay: 300 } config simulates real network latency. This helps you test:

  • Loading states in your UI
  • Race conditions
  • User experience with slow connections

Advanced Features

Filtering with Operators

const products = await products.list({
  filter: {
    price: { $gte: 100, $lte: 500 }, // Between $100 and $500
    category: { $in: ['Electronics', 'Books'] }, // Multiple categories
    inStock: true,
    rating: { $gt: 4.0 }, // 4+ stars only
  }
});

Sorting

const products = await products.list({
  sortBy: 'price',
  sortOrder: 'desc', // Highest price first
});

Pagination

const page1 = await products.list({ page: 1, limit: 10 });
const page2 = await products.list({ page: 2, limit: 10 });

Replace vs Update

// Update: Partial update, other fields preserved
await products.update(id, { price: 99.99 });

// Replace: Full replacement, unspecified fields removed
await products.replace(id, completeNewProductObject);

Cost Breakdown

Let's talk about actual costs:

Initial Setup:

  • 25 products with detailed schemas: ~$0.015-0.025
  • Runs once, then cached locally

Ongoing Development:

  • FREE! Data loads from local persistence
  • Only regenerate if you clear the cache or want new data

Comparison:

  • Traditional SaaS mock API: $10-50/month
  • Symulate BYOK: ~$0.05/month (occasional regenerations)
  • Savings: ~99% cost reduction

When to Upgrade to Symulate Platform

BYOK is perfect for most individual developers, but consider upgrading to Symulate Platform if you need:

  1. Cloud Persistence - Share data across your team
  2. Isolated Demos - Different data per git branch (perfect for agencies)
  3. Team Collaboration - Multiple developers working on shared mock data
  4. Priority Support - Get help from our team when you need it

You can start with BYOK and upgrade anytime. Your code doesn't change - just the configuration.

Complete Source Code

The complete working example is available in the Symulate SDK repository:

Next Steps

Now that you have a working CRUD application:

  1. Add More Collections - Create users, orders, reviews
  2. Build a Frontend - Connect React, Vue, or Angular
  3. Add Error Simulation - Test error handling:
    operations: {
      create: {
        errors: [
          {
            code: 400,
            failIf: (data) => !data.name,
            description: 'Product name is required'
          }
        ]
      }
    }
    
  4. Explore Custom Response Schemas - Shape responses exactly how you need them

Conclusion

With Symulate BYOK, you get:

  • βœ… Professional mock data - AI-generated, contextually perfect
  • βœ… Full CRUD operations - Production-ready API interface
  • βœ… Local development - Works offline after initial setup
  • βœ… Type-safe - Full TypeScript support
  • βœ… Minimal cost - ~$0.05/month for development
  • βœ… No vendor lock-in - It's your data, your infrastructure

All with just your OpenAI API key and 10 minutes of setup time.

Ready to build something amazing? Install Symulate SDK and get started:

npm install @symulate/sdk

Check out our Open Source documentation for more examples and guides.

Happy coding! πŸš€

Ready to try Symulate?

Start building frontends without waiting for backend APIs. Get 100K free AI tokens.

Sign Up Free