Skip to main content

Overview

InsForge Functions have built-in access to the InsForge SDK without requiring any imports. The SDK is pre-injected into the function runtime and available globally as createClient.

Quick Start

module.exports = async function(request) {
  // Extract user token from Authorization header
  const authHeader = request.headers.get('Authorization');
  const userToken = authHeader ? authHeader.replace('Bearer ', '') : null;
  
  // Create SDK client with user's token
  // This is required to access user-specific data
  const client = createClient({ 
    baseUrl: Deno.env.get('BACKEND_INTERNAL_URL') || 'http://insforge:7130',
    edgeFunctionToken: userToken  // Pass user's token for authentication
  });
  
  // Now you can access user data
  const { data: userData } = await client.auth.getCurrentUser();
  
  if (userData?.user?.id) {
    // User is authenticated, can perform user-specific operations
    return new Response(
      JSON.stringify({ userId: userData.user.id }),
      { headers: { 'Content-Type': 'application/json' } }
    );
  }
  
  return new Response('Unauthorized', { status: 401 });
}

Client Configuration

const client = createClient({
  baseUrl: Deno.env.get('BACKEND_INTERNAL_URL') || 'http://insforge:7130',
  edgeFunctionToken: userToken  // Required for authenticated operations
});
ParameterTypeDescription
baseUrlstringBackend API URL (use BACKEND_INTERNAL_URL env var or http://insforge:7130)
edgeFunctionTokenstringJWT token - either user’s token from Authorization header or ACCESS_API_KEY from env

Common Examples

Protected API Endpoint

Create an authenticated endpoint that returns user-specific data:
module.exports = async function(request) {
  // Get user token from request
  const authHeader = request.headers.get('Authorization');
  if (!authHeader?.startsWith('Bearer ')) {
    return new Response('Unauthorized', { status: 401 });
  }
  
  // Initialize SDK with user's token
  const client = createClient({ 
    baseUrl: 'http://insforge:7130',
    edgeFunctionToken: authHeader.replace('Bearer ', '')
  });
  
  // Verify user is authenticated
  const { data: userData } = await client.auth.getCurrentUser();
  if (!userData?.user?.id) {
    return new Response('Invalid token', { status: 401 });
  }
  
  // Return user-specific data
  const { data: userPosts } = await client.database
    .from('posts')
    .select('*')
    .eq('user_id', userData.user.id)
    .order('created_at', { ascending: false });
  
  return new Response(JSON.stringify(userPosts), {
    headers: { 'Content-Type': 'application/json' }
  });
}

Webhook Handler

Process incoming webhooks from external services:
module.exports = async function(request) {
  // Verify webhook signature
  const signature = request.headers.get('x-webhook-signature');
  const secret = Deno.env.get('WEBHOOK_SECRET');
  
  if (!signature || signature !== secret) {
    return new Response('Invalid signature', { status: 401 });
  }
  
  // Parse webhook payload
  const payload = await request.json();
  
  // For webhooks, use the ACCESS_API_KEY from environment
  const accessToken = Deno.env.get('ACCESS_API_KEY');
  const client = createClient({ 
    baseUrl: Deno.env.get('BACKEND_INTERNAL_URL') || 'http://insforge:7130',
    edgeFunctionToken: accessToken  // System access token for backend operations
  });
  
  // Process webhook event
  switch (payload.event) {
    case 'payment.success':
      await client.database
        .from('orders')
        .update({ status: 'paid' })
        .eq('id', payload.order_id);
      break;
      
    case 'user.created':
      await client.database
        .from('users')
        .insert([{ 
          external_id: payload.user_id,
          email: payload.email 
        }]);
      break;
  }
  
  return new Response('OK', { status: 200 });
}

File Upload Handler

Handle file uploads with validation:
module.exports = async function(request) {
  // Check authentication
  const token = request.headers.get('Authorization')?.replace('Bearer ', '');
  if (!token) {
    return new Response('Unauthorized', { status: 401 });
  }
  
  const client = createClient({ 
    baseUrl: 'http://insforge:7130',
    edgeFunctionToken: token
  });
  
  // Parse form data
  const formData = await request.formData();
  const file = formData.get('file');
  
  // Validate file
  if (!file || file.size > 5 * 1024 * 1024) {
    return new Response('Invalid file or too large (max 5MB)', { status: 400 });
  }
  
  // Upload to storage
  const { data, error } = await client.storage
    .from('user-uploads')
    .uploadAuto(file);
  
  if (error) {
    return new Response('Upload failed', { status: 500 });
  }
  
  return new Response(
    JSON.stringify({ url: data.url }),
    { headers: { 'Content-Type': 'application/json' } }
  );
}

API Gateway

Route different actions to different handlers:
module.exports = async function(request) {
  const url = new URL(request.url);
  const action = url.searchParams.get('action');
  
  const client = createClient({ 
    baseUrl: 'http://insforge:7130'
  });
  
  switch (action) {
    case 'search':
      const query = url.searchParams.get('q');
      const { data } = await client.database
        .from('products')
        .select('*')
        .ilike('name', `%${query}%`)
        .limit(10);
      
      return new Response(JSON.stringify(data), {
        headers: { 'Content-Type': 'application/json' }
      });
      
    case 'stats':
      const { count } = await client.database
        .from('orders')
        .select('*', { count: 'exact', head: true });
      
      return new Response(JSON.stringify({ total_orders: count }), {
        headers: { 'Content-Type': 'application/json' }
      });
      
    default:
      return new Response('Invalid action', { status: 400 });
  }
}

Environment Variables (Secrets)

Access secrets using Deno.env.get():
module.exports = async function(request) {
  // Access secret values
  const apiKey = Deno.env.get('STRIPE_API_KEY');
  const dbUrl = Deno.env.get('DATABASE_URL');
  const webhookSecret = Deno.env.get('WEBHOOK_SECRET');
  
  if (!apiKey) {
    return new Response('API key not configured', { status: 500 });
  }
  
  // Use secrets in your function
  const response = await fetch('https://api.stripe.com/v1/charges', {
    headers: {
      'Authorization': `Bearer ${apiKey}`
    }
  });
}

Request Handling

Request Object

module.exports = async function(request) {
  // Method: GET, POST, PUT, DELETE, etc.
  const method = request.method;
  
  // URL and query parameters
  const url = new URL(request.url);
  const page = url.searchParams.get('page');
  const search = url.searchParams.get('q');
  
  // Headers
  const contentType = request.headers.get('content-type');
  const authorization = request.headers.get('authorization');
  
  // Body parsing
  const json = await request.json();      // For JSON
  const text = await request.text();      // For plain text
  const formData = await request.formData(); // For multipart
}

Response Object

// JSON response
return new Response(
  JSON.stringify({ data: result }),
  { 
    status: 200,
    headers: { 'Content-Type': 'application/json' }
  }
);

// Text response
return new Response('Plain text', {
  headers: { 'Content-Type': 'text/plain' }
});

// HTML response
return new Response('<html>...</html>', {
  headers: { 'Content-Type': 'text/html' }
});

// No content
return new Response(null, { status: 204 });

// Error response
return new Response(
  JSON.stringify({ error: 'Not found' }),
  { 
    status: 404,
    headers: { 'Content-Type': 'application/json' }
  }
);

CORS Headers

const corsHeaders = {
  'Access-Control-Allow-Origin': '*',
  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
  'Access-Control-Allow-Headers': 'Content-Type, Authorization'
};

// Handle preflight
if (request.method === 'OPTIONS') {
  return new Response(null, { 
    status: 204, 
    headers: corsHeaders 
  });
}

// Add CORS to response
return new Response(JSON.stringify(data), {
  headers: {
    ...corsHeaders,
    'Content-Type': 'application/json'
  }
});

Error Handling

module.exports = async function(request) {
  try {
    const client = createClient({ 
      baseUrl: 'http://insforge:7130',
      edgeFunctionToken: request.headers.get('Authorization')?.replace('Bearer ', '')
    });
    
    const { data, error } = await client.database
      .from('posts')
      .select('*');
    
    // Check SDK errors
    if (error) {
      console.error('Database error:', error);
      return new Response(
        JSON.stringify({ error: error.message }),
        { status: 500, headers: { 'Content-Type': 'application/json' } }
      );
    }
    
    return new Response(JSON.stringify(data), {
      headers: { 'Content-Type': 'application/json' }
    });
    
  } catch (error) {
    // Handle unexpected errors
    console.error('Function error:', error);
    return new Response(
      JSON.stringify({ 
        error: 'Internal server error',
        message: error.message 
      }),
      { 
        status: 500,
        headers: { 'Content-Type': 'application/json' }
      }
    );
  }
}

Complete Example

module.exports = async function(request) {
  // Setup CORS
  const corsHeaders = {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type, Authorization'
  };
  
  // Handle OPTIONS
  if (request.method === 'OPTIONS') {
    return new Response(null, { status: 204, headers: corsHeaders });
  }
  
  // Get auth token
  const authHeader = request.headers.get('Authorization');
  const token = authHeader?.replace('Bearer ', '');
  
  if (!token) {
    return new Response('Unauthorized', { 
      status: 401,
      headers: corsHeaders 
    });
  }
  
  try {
    // Initialize SDK
    const client = createClient({ 
      baseUrl: 'http://insforge:7130',
      edgeFunctionToken: token
    });
    
    // Get current user
    const { data: userData, error: userError } = await client.auth.getCurrentUser();
    
    if (!userData?.user?.id) {
      return new Response('Invalid token', { 
        status: 401,
        headers: corsHeaders 
      });
    }
    
    // Parse request
    const url = new URL(request.url);
    const action = url.searchParams.get('action');
    
    switch (action) {
      case 'list': {
        // List user's posts
        const { data, error } = await client.database
          .from('posts')
          .select('*, users!inner(nickname)')
          .eq('user_id', userData.user.id)
          .order('created_at', { ascending: false });
        
        if (error) throw error;
        
        return new Response(JSON.stringify(data), {
          headers: { ...corsHeaders, 'Content-Type': 'application/json' }
        });
      }
      
      case 'create': {
        // Create new post
        const body = await request.json();
        
        const { data, error } = await client.database
          .from('posts')
          .insert([{
            user_id: userData.user.id,
            title: body.title,
            content: body.content
          }])
          .select()
          .single();
        
        if (error) throw error;
        
        return new Response(JSON.stringify(data), {
          status: 201,
          headers: { ...corsHeaders, 'Content-Type': 'application/json' }
        });
      }
      
      default:
        return new Response('Invalid action', { 
          status: 400,
          headers: corsHeaders 
        });
    }
    
  } catch (error) {
    console.error('Function error:', error);
    return new Response(
      JSON.stringify({ error: error.message }),
      { 
        status: 500,
        headers: { ...corsHeaders, 'Content-Type': 'application/json' }
      }
    );
  }
}

TypeScript Support

While TypeScript is supported, types are not checked at runtime:
interface RequestBody {
  title: string;
  content: string;
}

module.exports = async function(request: Request): Promise<Response> {
  const body = await request.json() as RequestBody;
  
  // TypeScript syntax works but no compile-time checking
  const response = {
    id: crypto.randomUUID(),
    title: body.title,
    content: body.content,
    created: new Date().toISOString()
  };
  
  return new Response(
    JSON.stringify(response),
    { headers: { 'Content-Type': 'application/json' } }
  );
}

Performance Tips

  1. Reuse client instances - Create client once per function execution
  2. Select only needed columns - Use .select('id, title') instead of *
  3. Use proper indexes - Ensure database indexes for filtered columns
  4. Batch operations - Insert/update multiple records in one call
  5. Handle errors early - Validate before expensive operations
  6. Use caching headers - Add Cache-Control for static responses

Debugging

// Use console.log for debugging
console.log('Function started');
console.log('Request:', {
  method: request.method,
  url: request.url,
  headers: Object.fromEntries(request.headers)
});

// Logs appear in Deno container
// View with: docker logs insforge-deno -f