Recipes

Complete, production-ready examples of common backend patterns and integrations. Copy and adapt these recipes for your own projects.

Authentication System

Complete User Registration

A robust registration endpoint with validation, duplicate checking, and token generation:

import { XanoScript } from './xano-script-library/lib/index.js';
 
const userRegistration = XanoScript.create('auth/register', 'POST')
  .description('Complete user registration with validation')
  .input('email', 'email', { required: true })
  .input('password', 'password', { required: true })
  .input('first_name', 'text', { required: true })
  .input('last_name', 'text', { required: true })
  
  // Validate password strength
  .precondition(
    'strlen($input.password) >= 8',
    'Password must be at least 8 characters'
  )
  
  // Check if email already exists
  .dbGet('"users"', { email: '$input.email' }, 'existing')
  .precondition('$existing == null', 'Email already registered')
  
  // Hash password and create user
  .hashPassword('$input.password', 'password_hash')
  .dbAdd('"users"', {
    email: '$input.email',
    password: '$password_hash',
    first_name: '$input.first_name',
    last_name: '$input.last_name',
    status: 'active',
    created_at: 'now'
  }, 'new_user')
  
  // Generate auth token
  .createToken({
    user_id: '$new_user.id',
    email: '$new_user.email',
    role: 'user'
  }, 86400, 'auth_token')
  
  .response({
    user: {
      id: '$new_user.id',
      email: '$new_user.email',
      first_name: '$new_user.first_name',
      last_name: '$new_user.last_name'
    },
    token: '$auth_token'
  });

User Login with Rate Limiting

Secure login with brute force protection:

const userLogin = XanoScript.create('auth/login', 'POST')
  .description('User login with rate limiting')
  .input('email', 'email', { required: true })
  .input('password', 'password', { required: true })
  
  // Check for rate limiting
  .dbGet('"login_attempts"', { email: '$input.email' }, 'attempts')
  
  .conditional('$attempts != null && $attempts.count >= 5')
    .then()
    .return({
      error: 'Too many login attempts. Try again in 15 minutes.'
    })
  .endConditional()
  
  // Find user
  .dbGet('"users"', { email: '$input.email' }, 'user')
  
  .conditional('$user == null')
    .then()
    // Record failed attempt
    .conditional('$attempts == null')
      .then()
      .dbAdd('"login_attempts"', {
        email: '$input.email',
        count: 1,
        last_attempt: 'now'
      }, 'new_attempt')
    .else()
      .dbEdit('"login_attempts"',
        { email: '$input.email' },
        {
          count: '$attempts.count + 1',
          last_attempt: 'now'
        },
        'updated_attempt'
      )
    .endConditional()
    .return({ error: 'Invalid credentials' })
  .endConditional()
  
  .precondition('$user.status == "active"', 'Account is inactive')
  
  // Verify password
  .verifyPassword('$input.password', '$user.password', 'is_valid')
  
  .conditional('!$is_valid')
    .then()
    // Record failed attempt
    .dbEdit('"login_attempts"',
      { email: '$input.email' },
      { count: '$attempts.count + 1' },
      'updated_attempt'
    )
    .return({ error: 'Invalid credentials' })
  .endConditional()
  
  // Clear failed attempts on successful login
  .conditional('$attempts != null')
    .then()
    .dbDelete('"login_attempts"', { email: '$input.email' }, 'cleared')
  .endConditional()
  
  // Update last login
  .dbEdit('"users"',
    { id: '$user.id' },
    { last_login: 'now' },
    'updated_user'
  )
  
  // Generate token
  .createToken({
    user_id: '$user.id',
    email: '$user.email',
    role: '$user.role'
  }, 86400, 'auth_token')
  
  .response({
    user: {
      id: '$user.id',
      email: '$user.email',
      first_name: '$user.first_name',
      last_name: '$user.last_name',
      role: '$user.role'
    },
    token: '$auth_token'
  });

Paginated Search API

Product Search with Filters

A complete search endpoint with pagination, filtering, and sorting:

const productSearch = XanoScript.create('products/search', 'GET')
  .description('Search products with filters and pagination')
  .input('search', 'text')
  .input('category', 'text')
  .input('min_price', 'decimal')
  .input('max_price', 'decimal')
  .input('status', 'text', { default: 'active' })
  .input('page', 'int', { default: 1 })
  .input('per_page', 'int', { default: 20 })
  .input('sort_by', 'text', { default: 'created_at' })
  .input('sort_dir', 'text', { default: 'desc' })
  
  // Build filters object
  .var('filters', 'object', { status: '$input.status' })
  
  .conditional('$input.category != null')
    .then()
    .var('filters', 'object', {
      status: '$input.status',
      category: '$input.category'
    })
  .endConditional()
  
  .conditional('$input.min_price != null && $input.max_price != null')
    .then()
    .var('filters', 'object', {
      status: '$input.status',
      category: '$input.category',
      price: '>= $input.min_price && <= $input.max_price'
    })
  .endConditional()
  
  // Query products
  .dbQuery('"products"', {
    search: '$input.search',
    filters: '$filters',
    pagination: {
      page: '$input.page',
      per_page: '$input.per_page'
    },
    sort: {
      field: '$input.sort_by',
      direction: '$input.sort_dir'
    }
  })
  
  .response({
    products: '$results.items',
    pagination: {
      page: '$results.curPage',
      per_page: '$results.perPage',
      total: '$results.itemsTotal',
      total_pages: 'ceil($results.itemsTotal / $results.perPage)',
      has_next: '$results.nextPage != null',
      has_prev: '$results.prevPage != null'
    },
    filters: {
      search: '$input.search',
      category: '$input.category',
      price_range: {
        min: '$input.min_price',
        max: '$input.max_price'
      }
    }
  });

Order Processing

Create Order with Validation

Process an order with inventory checking and total calculation:

const createOrder = XanoScript.create('orders/create', 'POST')
  .description('Create order with inventory validation')
  .requiresAuth('users')
  .input('items', 'object', { required: true })
  .input('shipping_address', 'object', { required: true })
  
  // Validate items not empty
  .precondition('count($input.items) > 0', 'Order must have at least one item')
  
  .var('total', 'decimal', 0)
  .var('validated_items', 'array', [])
  
  // Validate each item and calculate total
  .forEach('$input.items', '$item')
    
    .precondition('$item.product_id != null', 'Product ID required')
    .precondition('$item.quantity > 0', 'Quantity must be positive')
    
    // Get product
    .dbGet('"products"', { id: '$item.product_id' }, 'product')
    .precondition('$product != null', 'Product not found')
    .precondition('$product.status == "active"', 'Product not available')
    
    // Check inventory
    .precondition(
      '$product.stock >= $item.quantity',
      'Insufficient stock for product: $product.name'
    )
    
    // Calculate item total
    .var('item_total', 'decimal', '$product.price * $item.quantity')
    .var('total', 'decimal', '$total + $item_total')
    
    // Add to validated items
    .var('validated_item', 'object', {
      product_id: '$product.id',
      product_name: '$product.name',
      quantity: '$item.quantity',
      price: '$product.price',
      total: '$item_total'
    })
    .arrayPush('$validated_items', '$validated_item')
    
  .endForEach()
  
  // Create order
  .dbAdd('"orders"', {
    user_id: '$auth.id',
    total: '$total',
    status: 'pending',
    shipping_address: '$input.shipping_address',
    created_at: 'now'
  }, 'new_order')
  
  // Create order items and update inventory
  .forEach('$validated_items', '$item')
    
    .dbAdd('"order_items"', {
      order_id: '$new_order.id',
      product_id: '$item.product_id',
      quantity: '$item.quantity',
      price: '$item.price',
      total: '$item.total'
    }, 'order_item')
    
    // Reduce stock
    .dbGet('"products"', { id: '$item.product_id' }, 'product')
    .dbEdit('"products"',
      { id: '$item.product_id' },
      { stock: '$product.stock - $item.quantity' },
      'updated_product'
    )
    
  .endForEach()
  
  .response({
    order: {
      id: '$new_order.id',
      total: '$new_order.total',
      status: '$new_order.status',
      created_at: '$new_order.created_at'
    },
    items: '$validated_items',
    item_count: 'count($validated_items)'
  });

Process Bulk Orders

Handle multiple orders in one request:

const bulkProcessOrders = XanoScript.create('orders/bulk-process', 'POST')
  .description('Process multiple orders with status update')
  .requiresAuth('users')
  .input('order_ids', 'object', { required: true })
  .input('action', 'text', { required: true })
  
  // Verify admin role
  .precondition('$auth.role == "admin"', 'Admin access required')
  
  // Validate action
  .precondition(
    '$input.action == "approve" || $input.action == "cancel" || $input.action == "ship"',
    'Invalid action'
  )
  
  .var('processed', 'int', 0)
  .var('failed', 'int', 0)
  .var('results', 'array', [])
  
  .forEach('$input.order_ids', '$order_id')
    
    // Get order
    .dbGet('"orders"', { id: '$order_id' }, 'order')
    
    .conditional('$order == null')
      .then()
      .var('failed', 'int', '$failed + 1')
      .var('result', 'object', {
        order_id: '$order_id',
        success: false,
        reason: 'Order not found'
      })
      .arrayPush('$results', '$result')
    .elseIf('$order.status == "cancelled"')
      .then()
      .var('failed', 'int', '$failed + 1')
      .var('result', 'object', {
        order_id: '$order_id',
        success: false,
        reason: 'Order already cancelled'
      })
      .arrayPush('$results', '$result')
    .else()
      // Process order
      .dbEdit('"orders"',
        { id: '$order_id' },
        {
          status: '$input.action',
          processed_by: '$auth.id',
          processed_at: 'now'
        },
        'updated_order'
      )
      .var('processed', 'int', '$processed + 1')
      .var('result', 'object', {
        order_id: '$order_id',
        success: true,
        new_status: '$input.action'
      })
      .arrayPush('$results', '$result')
    .endConditional()
    
  .endForEach()
  
  .response({
    total: 'count($input.order_ids)',
    processed: '$processed',
    failed: '$failed',
    results: '$results'
  });

External API Integration

Weather Service Integration

Fetch and cache weather data:

const getWeather = XanoScript.create('integrations/weather', 'GET')
  .description('Get weather data with caching')
  .input('city', 'text', { required: true })
  
  .var('cache_key', 'text', 'weather_$input.city')
  
  // Check cache
  .redisGet('$cache_key', 'cached_weather')
  
  .conditional('$cached_weather != null')
    .then()
    .response({
      temperature: '$cached_weather.temp',
      description: '$cached_weather.description',
      city: '$input.city',
      cached: true
    })
  .endConditional()
  
  // Fetch from API
  .var('api_key', 'text', 'env.WEATHER_API_KEY')
  
  .apiRequest(
    'https://api.openweathermap.org/data/2.5/weather',
    'GET',
    {
      params: {
        q: '$input.city',
        appid: '$api_key',
        units: 'metric'
      }
    },
    'api_response'
  )
  
  .conditional('$api_response.response.status != 200')
    .then()
    .return({
      error: 'Failed to fetch weather data',
      status: '$api_response.response.status'
    })
  .endConditional()
  
  // Cache result for 30 minutes
  .var('weather_data', 'object', {
    temp: '$api_response.response.data.main.temp',
    description: '$api_response.response.data.weather[0].description'
  })
  
  .redisSet('$cache_key', '$weather_data', 1800)
  
  .response({
    temperature: '$weather_data.temp',
    description: '$weather_data.description',
    city: '$api_response.response.data.name',
    cached: false
  });

Stripe Payment Processing

Create and process a payment:

const createPaymentIntent = XanoScript.create('payments/create', 'POST')
  .description('Create Stripe payment intent')
  .requiresAuth('users')
  .input('amount', 'int', { required: true })
  .input('currency', 'text', { default: 'usd' })
  .input('description', 'text')
  
  // Validate amount
  .precondition('$input.amount >= 50', 'Minimum amount is $0.50')
  
  .var('stripe_key', 'text', 'env.STRIPE_SECRET_KEY')
  
  // Create payment intent
  .apiRequest(
    'https://api.stripe.com/v1/payment_intents',
    'POST',
    {
      headers: [
        { key: 'Authorization', value: 'Bearer $stripe_key' },
        { key: 'Content-Type', value: 'application/x-www-form-urlencoded' }
      ],
      body: {
        amount: '$input.amount',
        currency: '$input.currency',
        description: '$input.description',
        metadata: {
          user_id: '$auth.id',
          user_email: '$auth.email'
        }
      }
    },
    'stripe_response'
  )
  
  .conditional('$stripe_response.response.status != 200')
    .then()
    .return({
      error: 'Failed to create payment intent',
      message: '$stripe_response.response.data.error.message'
    })
  .endConditional()
  
  .var('payment_intent', 'object', '$stripe_response.response.data')
  
  // Save to database
  .dbAdd('"payments"', {
    user_id: '$auth.id',
    stripe_payment_intent_id: '$payment_intent.id',
    amount: '$input.amount',
    currency: '$input.currency',
    status: '$payment_intent.status',
    description: '$input.description',
    created_at: 'now'
  }, 'payment_record')
  
  .response({
    payment_id: '$payment_record.id',
    client_secret: '$payment_intent.client_secret',
    amount: '$input.amount',
    currency: '$input.currency',
    status: '$payment_intent.status'
  });

Role-Based Access Control

Advanced Permission System

Implement granular permission checking:

const checkPermission = XanoScript.create('auth/check-permission', 'POST')
  .description('Check user permissions for resource and action')
  .requiresAuth('users')
  .input('resource', 'text', { required: true })
  .input('action', 'text', { required: true })
  .input('resource_id', 'int')
  
  .var('has_permission', 'bool', false)
  
  // Admin has all permissions
  .conditional('$auth.role == "admin"')
    .then()
    .var('has_permission', 'bool', true)
  
  // Moderators can read and update
  .elseIf('$auth.role == "moderator"')
    .then()
    .conditional('$input.action == "read" || $input.action == "update"')
      .then()
      .var('has_permission', 'bool', true)
    .endConditional()
  
  // Users have limited permissions
  .elseIf('$auth.role == "user"')
    .then()
    
    .conditional('$input.action == "read"')
      .then()
      .var('has_permission', 'bool', true)
    
    .elseIf('$input.action == "update" || $input.action == "delete"')
      .then()
      // Check resource ownership
      .conditional('$input.resource_id != null')
        .then()
        .dbGet('"$input.resource"', { id: '$input.resource_id' }, 'resource')
        .conditional('$resource != null && $resource.user_id == $auth.id')
          .then()
          .var('has_permission', 'bool', true)
        .endConditional()
      .endConditional()
    
    .endConditional()
  
  .endConditional()
  
  .response({
    granted: '$has_permission',
    resource: '$input.resource',
    action: '$input.action',
    user: {
      id: '$auth.id',
      role: '$auth.role'
    }
  });

Next Steps