Skip to content

Cookbook: The Brownfield Endpoint ​

One of the biggest misconceptions about new frameworks is that you have to rewrite your entire application to use them.

Carotene is designed for incremental adoption. If you have an existing Next.js, Express, or Spring Boot application, you do not need to replace your database, your auth stack, or your frontend to use Carotene.

You can drop Carotene in to generate the logic for a single, highly-complex endpoint (like an AI agent, a complex pricing calculator, or a PDF generator) and proxy requests to it from your existing application.

1. The Architecture Blueprint ​

In this scenario, we have a legacy Node.js/Express application. We want to add a new AI-powered feature: a "Smart Recipe Generator" that takes a list of ingredients and returns a structured recipe.

Instead of writing this complex prompt engineering and validation logic in Node.js, we define a single gateway and backend in Carotene. We don't define a store because we are passing the data directly in the request payload.

dart
// smart-recipe.carrot

backend RecipeService {

  // We define the strict expected output format
  model Recipe {
    title: String
    prepTimeMinutes: Int
    instructions: [String]
    dietaryTags: [String]
  }

  // Define the integration to the AI provider
  integration OpenAI {
    GenerateJSON(prompt: String, schema: JSON) -> JSON
  }

  // This function takes raw ingredients and returns a strict Recipe
  function GenerateRecipe(ingredients: [String]) -> Recipe {
    calls integration.OpenAI

    // Mathematical bounds to prevent infinite loops or absurd outputs
    ensures result.prepTimeMinutes > 0
    ensures result.prepTimeMinutes <= 180
    ensures result.instructions.length > 0

    implements {
      // The Generative Operator instructs the AI developer (Carotene) to write the implementation.
      // We instruct it to use the OpenAI integration to generate the recipe.
      recipe = @("Construct a prompt asking a professional chef to create a creative recipe using ONLY the provided 'ingredients'. Call the OpenAI integration with the prompt and the Recipe JSON schema, and parse the returned JSON into the Recipe model.")
      
      return recipe
    }
  }
  
  // We expose this specific function as an HTTP endpoint
  gateway ProxyIngress {
    rest {
      POST /api/v1/generate-recipe -> GenerateRecipe
    }
  }
}

2. The Proof (Testing the Logic) ​

Before we integrate this into our Express app, we write a test block to ensure the Generative Operator reliably outputs the correct schema and respects our ensures bounds.

dart
test "Generates a valid recipe from basic ingredients" {
  // We don't need a database, just pass the ingredients
  myIngredients = ["Eggs", "Flour", "Milk", "Cheese"]
  
  result = GenerateRecipe(myIngredients)
  
  // Verify the AI respected the type schema and bounds
  asserts result.title != ""
  asserts result.prepTimeMinutes > 0
  asserts result.instructions.length > 2
}

3. The Brownfield Integration ​

When you run carrot generate, Carotene builds a standalone, zero-dependency microservice containing just this endpoint.

You can run this Carotene service on an internal port (e.g., 8080) or deploy it as a Serverless function.

Then, in your existing Node.js/Express app, you simply proxy the request to the Carotene endpoint. Your existing app handles the authentication, session management, and rate limiting.

javascript
// Your existing Express.js App (app.js)
const express = require('express');
const axios = require('axios');
const { requireAuth } = require('./middleware/auth');

const app = express();
app.use(express.json());

// Legacy endpoints remain untouched
app.get('/api/users', requireAuth, (req, res) => { ... });

// New AI Feature: Proxy to Carotene
app.post('/recipes/smart-generate', requireAuth, async (req, res) => {
  const { ingredients } = req.body;
  
  try {
    // Call the locally running Carotene microservice
    const caroteneResponse = await axios.post('http://localhost:8080/api/v1/generate-recipe', {
      ingredients
    });
    
    // Carotene guarantees the response matches the Recipe schema
    res.json(caroteneResponse.data);
  } catch (error) {
    res.status(500).json({ error: "Recipe generation failed" });
  }
});

4. The Result ​

By using Carotene as a "sidecar" for complex logic generation:

  1. Zero Rip-and-Replace: Your existing stack, database, and auth remain entirely unchanged.
  2. Guaranteed Output: Carotene's strict type system and ensures bounds guarantee that your Express app will always receive a perfectly formatted JSON Recipe object, preventing frontend crashes from malformed LLM outputs.
  3. Painless Prompt Engineering: You didn't have to write manual validation logic, retry loops, or system prompts in Node.js. Carotene handled the entire generative lifecycle automatically.