Leon4gr45/builder
0
1# Backend Features2 3This guide covers OSW Studio's advanced backend features, available in **Server Mode** only.4 5## Overview6 7Server Mode unlocks powerful backend capabilities for your published deployments, including serverless API endpoints, database management, and secure secrets storage.8 9**Key Features:**10- **Edge Functions** - REST API endpoints with JavaScript runtime11- **Database** - Per-deployment SQLite with SQL editor and schema browser12- **Server Functions** - Reusable helper code for edge functions13- **Scheduled Functions** - Run edge functions on cron schedules14- **Secrets** - Encrypted storage for API keys and tokens15- **Logs** - Execution history and debugging16- **AI Integration** - AI awareness of backend features via `/.server/` folder17 18## Where Backend Features Live19 20Backend features are **project-scoped** — they belong to the project, not to a specific deployment. When you publish the project, its backend features are extracted into the deployment's runtime database. The same project can be published multiple times; each deployment gets a snapshot of the project's backend features at publish time.21 22You can manage backend features in two places:23 241. **Project Settings** (primary, in the workspace): Click **Project Settings** in the workspace header to open a modal with Schema, Functions, Helpers, Secrets, and Schedules tabs. Changes take effect in the project and will be included in the next publish.252. **Deployment Server Settings** (for an already-published deployment): Open the deployment in the Admin Dashboard to inspect and tweak the runtime copy of its backend. This is useful for rotating secrets or debugging a published deployment without re-publishing from the project.26 27## Prerequisites28 29- OSW Studio running in **Server Mode**30- A project with Backend enabled (toggle in Project Settings)31 32## Accessing Server Settings33 34**From the workspace (recommended):**351. Open your project362. Click **Project Settings** in the workspace header373. Toggle **Backend Enabled** if it isn't already384. Use the tabs to create edge functions, server functions, secrets, and schedules39 40**From the Admin Dashboard (for published deployments):**411. Open the **Admin Dashboard** (`/admin`) or `/w/{workspaceId}/deployments`422. Navigate to **Deployments** and select your deployment433. Click the **Server Settings** button (server icon) next to Deployment Settings44 - The server icon only appears for published deployments with database enabled45 - You can also access it via the "..." dropdown menu > "Server Settings"46 47The Server Settings modal contains seven tabs:48- **Schema** - Browse tables and columns49- **SQL** - Execute raw SQL queries50- **Functions** - Create and manage edge functions (HTTP endpoints)51- **Helpers** - Create and manage server functions (reusable code)52- **Secrets** - Store encrypted API keys and tokens53- **Schedules** - Create and manage scheduled functions (cron jobs)54- **Logs** - View function execution history55 56---57 58## Edge Functions59 60### Creating a Function61 621. Go to **Server Settings > Functions**632. Click **New Function**643. Configure:65 - **Name**: Lowercase letters, numbers, and hyphens (e.g., `get-users`)66 - **HTTP Method**: GET, POST, PUT, DELETE, or ANY67 - **Description**: Optional description68 - **Timeout**: 1-30 seconds (default: 5s)69 - **Code**: JavaScript function body70 714. Click **Create Function**72 73### Function URL74 75Each function is accessible at:76```77https://your-server.com/api/deployments/{deploymentId}/functions/{function-name}78```79 80For example:81```82https://your-instance.com/api/deployments/abc123/functions/get-products83```84 85### Calling Edge Functions from Published Sites86 87Published sites automatically route edge function calls! Your frontend JavaScript can call functions using simple paths:88 89```javascript90// In your published site's JavaScript91const response = await fetch('/submit-contact', {92 method: 'POST',93 headers: { 'Content-Type': 'application/json' },94 body: JSON.stringify({ name: 'John', email: 'john@example.com' })95});96const result = await response.json();97```98 99This works because OSW Studio injects a lightweight interceptor script (~1.5KB) into published HTML files that:100- Detects requests that look like edge function calls (paths without file extensions)101- Routes them to `/api/deployments/{deploymentId}/functions/{path}`102- Works with `fetch()`, `XMLHttpRequest`, and form submissions103 104**Form submissions** are also intercepted:105```html106<form action="/submit-contact" method="POST">107 <input name="email" type="email" required>108 <button type="submit">Subscribe</button>109</form>110```111 112The form data is automatically converted to JSON and sent to your edge function.113 114**Custom event handling:**115```javascript116// Listen for edge function responses117document.addEventListener('edge-function-response', (e) => {118 console.log('Result:', e.detail.result);119});120 121document.addEventListener('edge-function-error', (e) => {122 console.error('Error:', e.detail.error);123});124```125 126### Available APIs127 128Your function code has access to these global objects:129 130#### `request` Object131```javascript132request.method // HTTP method (GET, POST, etc.)133request.body // Parsed JSON body (POST/PUT/PATCH)134request.query // Query string parameters135request.headers // Request headers136request.path // URL path after function name137request.params // Path parameters (if any)138```139 140#### `db` Object (Database)141```javascript142// Execute SELECT queries143const users = db.query('SELECT * FROM users WHERE active = ?', [true]);144const user = db.all('SELECT * FROM users LIMIT 10'); // alias for query145 146// Execute INSERT/UPDATE/DELETE147const result = db.run('INSERT INTO users (name, email) VALUES (?, ?)', ['John', 'john@example.com']);148// result = { changes: 1 }149```150 151#### `Response` Object152```javascript153// Return JSON154Response.json({ users: [...] });155Response.json({ error: 'Not found' }, 404);156 157// Return plain text158Response.text('Hello World');159Response.text('Created', 201);160 161// Return error162Response.error('Something went wrong', 500);163Response.error('Unauthorized', 401);164```165 166#### `fetch` Function167```javascript168// Make external HTTP requests169const response = await fetch('https://api.example.com/data');170const data = await response.json();171Response.json(data);172 173// With options174const res = await fetch('https://api.example.com/users', {175 method: 'POST',176 headers: { 'Content-Type': 'application/json' },177 body: { name: 'John', email: 'john@example.com' }178});179```180 181**Security Limits:**182- Max 10 requests per function execution183- 10 second timeout per request184- 5MB max response body185- Only `http://` and `https://` protocols allowed186- Private IPs blocked in production (localhost, 10.x.x.x, 172.16-31.x.x, 192.168.x.x, 169.254.x.x)187- Development mode allows local requests for testing188 189#### `atob` / `btoa` Functions190```javascript191// Base64 encode192const encoded = btoa('Hello World'); // "SGVsbG8gV29ybGQ="193 194// Base64 decode195const decoded = atob('SGVsbG8gV29ybGQ='); // "Hello World"196```197 198#### `server` Object (Helper Functions)199```javascript200// Call server functions (helpers) defined in the Helpers tab201const auth = server.validateAuth(request.headers['x-api-key']);202const formatted = server.formatPrice(29.99, 'USD');203const user = server.getUserById(123);204```205 206See [Server Functions (Helpers)](#server-functions-helpers) for more details.207 208#### `secrets` Object (Encrypted Secrets)209```javascript210// Get secret value by name211const apiKey = secrets.get('STRIPE_API_KEY');212if (!apiKey) {213 Response.error('Stripe not configured', 500);214 return;215}216 217// Check if secret exists218if (secrets.has('SENDGRID_KEY')) {219 // Use SendGrid220}221 222// List all available secret names223const allSecrets = secrets.list(); // ['STRIPE_API_KEY', 'SENDGRID_KEY', ...]224```225 226See [Secrets](#secrets) for more details.227 228### Example Functions229 230#### List Items (GET)231```javascript232// GET /api/deployments/{deploymentId}/functions/list-items233const items = db.query('SELECT * FROM items ORDER BY created_at DESC LIMIT 20');234Response.json({ items });235```236 237#### Create Item (POST)238```javascript239// POST /api/deployments/{deploymentId}/functions/create-item240if (!request.body.name) {241 Response.error('Name is required', 400);242 return;243}244 245const result = db.run(246 'INSERT INTO items (name, description) VALUES (?, ?)',247 [request.body.name, request.body.description || '']248);249 250Response.json({251 id: result.lastInsertRowid,252 message: 'Item created'253}, 201);254```255 256#### Get Item by ID (GET with path)257```javascript258// GET /api/deployments/{deploymentId}/functions/get-item/123259const id = request.path.split('/')[1];260if (!id) {261 Response.error('ID required', 400);262 return;263}264 265const item = db.query('SELECT * FROM items WHERE id = ?', [id]);266if (item.length === 0) {267 Response.error('Item not found', 404);268 return;269}270 271Response.json(item[0]);272```273 274#### External API Proxy275```javascript276// GET /api/deployments/{deploymentId}/functions/weather?city=London277const city = request.query.city || 'New York';278const apiKey = secrets.get('WEATHER_API_KEY');279if (!apiKey) {280 Response.error('Weather API not configured', 500);281 return;282}283 284const res = await fetch(285 `https://api.weatherapi.com/v1/current.json?key=${apiKey}&q=${city}`286);287const data = await res.json();288 289Response.json({290 city: data.location.name,291 temp: data.current.temp_c,292 condition: data.current.condition.text293});294```295 296### Security Considerations297 298#### Sandboxed Execution299- Functions run in a **QuickJS WebAssembly sandbox** - a completely separate JavaScript engine300- True isolation via WASM boundary: no shared memory or access to Node.js internals301- Memory limits enforced by WASM (64MB default)302- Execution time limits with interrupt handler (configurable 1-30 seconds)303- Allowed globals: `JSON`, `Date`, `Math`, `Array`, `Object`, `String`, `Number`, `Boolean`, `RegExp`, `Error`, `Map`, `Set`, `Promise`, `Symbol`, `console`304- Network: `fetch` (with security limits - see above)305- Utility functions: `parseInt`, `parseFloat`, `isNaN`, `isFinite`, `encodeURIComponent`, `decodeURIComponent`, `encodeURI`, `decodeURI`306- Base64: `atob` (decode), `btoa` (encode)307- No access to: `require`, `process`, `__dirname`, `Buffer`, file system308- `setTimeout`/`setInterval` disabled (prevents runaway execution)309 310#### Database Protection311System tables are protected and cannot be accessed:312- `site_info`313- `files`314- `file_tree_nodes`315- `pageviews`316- `interactions`317- `sessions`318- `edge_functions`319- `function_logs`320- `server_functions`321- `secrets`322 323Only user-created tables are accessible via the `db` API.324 325#### Query Limits326- Maximum 100 database queries per function execution327- Timeout enforced (1-30 seconds, configurable)328- SQL keywords validated to prevent dangerous operations329 330### Managing Functions331 332#### Enable/Disable333Click the dropdown menu on a function card and select **Enable** or **Disable**. Disabled functions return 404.334 335#### Edit336Click **Edit** in the dropdown to modify the function code, method, or timeout.337 338#### Delete339Click **Delete** in the dropdown. This cannot be undone.340 341#### Copy URL342Click **Copy URL** below the function card to copy the public endpoint URL.343 344---345 346## Server Functions (Helpers)347 348Server functions are reusable JavaScript helpers that can be called from your edge functions via the `server` object. They enable code reuse across multiple edge functions.349 350### Creating a Server Function351 3521. Go to **Server Settings > Helpers**3532. Click **New Helper**3543. Configure:355 - **Name**: Valid JavaScript identifier (camelCase or snake_case, e.g., `validateAuth`, `format_price`)356 - **Description**: Optional description357 - **Code**: JavaScript function body3584. Click **Create Function**359 360### How Server Functions Work361 362Server functions receive arguments via an `args` array and have access to `db`, `fetch`, and `console`. They return a value that is passed back to the calling edge function.363 364```javascript365// Server function "validateAuth"366const [apiKey] = args;367if (!apiKey) {368 return { valid: false, error: 'No API key provided' };369}370 371const users = db.query('SELECT * FROM users WHERE api_key = ?', [apiKey]);372if (users.length === 0) {373 return { valid: false, error: 'Invalid API key' };374}375 376return { valid: true, user: users[0] };377```378 379### Calling from Edge Functions380 381Server functions are available on the `server` object. Pass arguments as regular function parameters:382 383```javascript384// Edge function code385const auth = server.validateAuth(request.headers['x-api-key']);386if (!auth.valid) {387 Response.error(auth.error, 401);388 return;389}390 391// User is authenticated392const products = db.query(393 'SELECT * FROM products WHERE user_id = ?',394 [auth.user.id]395);396Response.json({ products });397```398 399### Available APIs in Server Functions400 401| API | Description |402|-----|-------------|403| `args` | Array of arguments passed from edge function |404| `db.query()` | Execute SELECT query |405| `db.run()` | Execute INSERT/UPDATE/DELETE |406| `db.all()` | Alias for query |407| `fetch()` | Make external HTTP requests |408| `console.log()` | Log messages (visible in function logs) |409 410### Example Server Functions411 412#### Validate API Key413```javascript414// Name: validateAuth415const [apiKey] = args;416if (!apiKey) return { valid: false };417 418const users = db.query('SELECT id, name, role FROM users WHERE api_key = ?', [apiKey]);419return users.length > 0 ? { valid: true, user: users[0] } : { valid: false };420```421 422#### Format Price423```javascript424// Name: formatPrice425const [amount, currency = 'USD'] = args;426const symbols = { USD: '$', EUR: '€', GBP: '£', JPY: '¥' };427const symbol = symbols[currency] || currency + ' ';428return symbol + amount.toFixed(2);429```430 431#### Get User by ID432```javascript433// Name: getUserById434const [id] = args;435if (!id) return null;436 437const users = db.query('SELECT * FROM users WHERE id = ?', [id]);438return users.length > 0 ? users[0] : null;439```440 441#### Check Permission442```javascript443// Name: hasPermission444const [userId, permission] = args;445if (!userId || !permission) return false;446 447const perms = db.query(448 'SELECT 1 FROM user_permissions WHERE user_id = ? AND permission = ?',449 [userId, permission]450);451return perms.length > 0;452```453 454### Security Notes455 456- Server functions run in the same QuickJS WASM context as the parent edge function457- They share the total execution timeout (not additive)458- Recursive calls are possible but limited by timeout459- The `server_functions` table is protected and cannot be queried460- Only enabled server functions are available to edge functions461 462### Managing Server Functions463 464- **Enable/Disable**: Toggle from the dropdown menu. Disabled functions are not available to edge functions.465- **Edit**: Click Edit to modify the code or description466- **Delete**: Click Delete to remove (cannot be undone)467 468---469 470## Secrets471 472Secrets provide secure, encrypted storage for sensitive values like API keys, tokens, and passwords. Edge functions can access secrets via the `secrets` object without exposing the actual values in your code.473 474### Prerequisites475 476Before using secrets, you must set the `SECRETS_ENCRYPTION_KEY` environment variable:477 478```bash479# Generate a secure 256-bit key480node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"481 482# Add to your environment483export SECRETS_ENCRYPTION_KEY="your-generated-key-here"484```485 486### Creating a Secret487 4881. Go to **Server Settings > Secrets**4892. Click **New Secret**4903. Configure:491 - **Name**: SCREAMING_SNAKE_CASE (e.g., `STRIPE_API_KEY`, `SENDGRID_TOKEN`)492 - **Value**: The secret value (will be encrypted)493 - **Description**: Optional description4944. Click **Create Secret**495 496### Using Secrets in Edge Functions497 498The `secrets` object is available in all edge functions:499 500```javascript501// Get a secret value502const apiKey = secrets.get('STRIPE_API_KEY');503 504// Check if a secret exists505if (secrets.has('STRIPE_API_KEY')) {506 // Use the secret507}508 509// List all available secret names (not values)510const names = secrets.list(); // ['STRIPE_API_KEY', 'SENDGRID_TOKEN', ...]511```512 513### Example: Stripe API Integration514 515```javascript516// POST /api/deployments/{deploymentId}/functions/create-charge517const stripeKey = secrets.get('STRIPE_API_KEY');518if (!stripeKey) {519 Response.error('Stripe not configured', 500);520 return;521}522 523const { amount, currency, source } = request.body;524if (!amount || !source) {525 Response.error('Amount and source are required', 400);526 return;527}528 529const res = await fetch('https://api.stripe.com/v1/charges', {530 method: 'POST',531 headers: {532 'Authorization': `Bearer ${stripeKey}`,533 'Content-Type': 'application/x-www-form-urlencoded',534 },535 body: new URLSearchParams({536 amount: String(amount),537 currency: currency || 'usd',538 source,539 }),540});541 542const charge = await res.json();543Response.json({ charge });544```545 546### Secrets API Reference547 548| Method | Description |549|--------|-------------|550| `secrets.get(name)` | Get secret value, or `null` if not found |551| `secrets.has(name)` | Check if secret exists (returns boolean) |552| `secrets.list()` | Get array of all secret names (not values) |553 554### Security Notes555 556- **Encryption**: Secrets are encrypted using AES-256-GCM with unique IVs per secret557- **Never logged**: Secret values are never written to logs or exposed in API responses558- **Admin-only**: Only authenticated admins can create, view (metadata only), or delete secrets559- **Protected table**: The `secrets` table cannot be queried directly from edge functions560- **Key management**: The master encryption key must be stored securely as an environment variable561 562### Managing Secrets563 564- **Edit**: Click Edit in the dropdown to update the value or description (name cannot be changed)565- **Delete**: Click Delete to permanently remove (cannot be undone)566- **No value display**: Secret values are never displayed after creation for security567 568---569 570## Scheduled Functions (Cron Jobs)571 572Scheduled functions run edge functions automatically on a cron schedule. Use them for periodic tasks like database cleanup, report generation, cache warming, or external API syncing.573 574### Creating a Scheduled Function575 5761. Go to **Server Settings > Schedules**5772. Click **New Schedule**5783. Configure:579 - **Name**: Lowercase letters, numbers, and hyphens (e.g., `daily-cleanup`)580 - **Edge Function**: Select which edge function to invoke581 - **Cron Expression**: Standard 5-field cron syntax (e.g., `0 8 * * *`)582 - **Timezone**: IANA timezone (default: `UTC`)583 - **Description**: Optional description584 - **Config**: Optional JSON object passed as the request body5854. Click **Create Schedule**586 587### How It Works588 589When a scheduled function fires:5901. The cron scheduler triggers at the specified time5912. The linked edge function is invoked with the `config` object as `request.body`5923. The execution result (success/error) and duration are recorded5934. The next run time is calculated from the cron expression594 595The edge function runs in the same QuickJS sandbox as HTTP-triggered invocations, with full access to `db`, `fetch`, `secrets`, `server`, and `console`.596 597### Cron Expression Reference598 599Cron expressions use 5 fields: `minute hour day-of-month month day-of-week`600 601**Minimum interval: 5 minutes.** Expressions that resolve to intervals shorter than 5 minutes will be rejected.602 603| Expression | Description |604|------------|-------------|605| `*/5 * * * *` | Every 5 minutes |606| `0 * * * *` | Every hour (at minute 0) |607| `0 8 * * *` | Daily at 8:00 AM |608| `0 0 * * *` | Daily at midnight |609| `30 9 * * 1-5` | Weekdays at 9:30 AM |610| `0 0 * * 1` | Every Monday at midnight |611| `0 0 1 * *` | First of every month at midnight |612| `0 0 1 1 *` | January 1st at midnight (yearly) |613 614**Field ranges:**615- Minute: 0-59616- Hour: 0-23617- Day of month: 1-31618- Month: 1-12619- Day of week: 0-7 (0 and 7 = Sunday)620 621### Example Scheduled Functions622 623#### Daily Database Cleanup624Clean up old records every day at 3:00 AM UTC:625 626- **Edge Function** (`cleanup`):627```javascript628const daysToKeep = request.body.daysToKeep || 30;629const cutoff = new Date(Date.now() - daysToKeep * 86400000).toISOString();630 631const result = db.run('DELETE FROM logs WHERE created_at < ?', [cutoff]);632Response.json({ deleted: result.changes, cutoff });633```634 635- **Schedule config**:636 - Cron: `0 3 * * *`637 - Config: `{ "daysToKeep": 30 }`638 639#### Hourly Stats Aggregation640Aggregate analytics data every hour:641 642- **Edge Function** (`aggregate-stats`):643```javascript644const hourAgo = new Date(Date.now() - 3600000).toISOString();645const stats = db.query('SELECT COUNT(*) as views FROM pageviews WHERE timestamp > ?', [hourAgo]);646 647db.run('INSERT INTO hourly_stats (hour, views) VALUES (?, ?)',648 [new Date().toISOString().slice(0, 13), stats[0].views]);649 650Response.json({ aggregated: true, views: stats[0].views });651```652 653- **Schedule config**:654 - Cron: `0 * * * *`655 - Config: `{}`656 657#### Weekly Report Email658Send a weekly summary every Monday at 9:00 AM:659 660- **Edge Function** (`send-weekly-report`):661```javascript662const apiKey = secrets.get('SENDGRID_KEY');663if (!apiKey) { Response.error('Email not configured', 500); return; }664 665const stats = db.query('SELECT COUNT(*) as total FROM orders WHERE created_at > datetime("now", "-7 days")');666 667const res = await fetch('https://api.sendgrid.com/v3/mail/send', {668 method: 'POST',669 headers: { 'Authorization': 'Bearer ' + apiKey, 'Content-Type': 'application/json' },670 body: JSON.stringify({671 personalizations: [{ to: [{ email: request.body.recipient }] }],672 from: { email: 'reports@example.com' },673 subject: 'Weekly Report',674 content: [{ type: 'text/plain', value: 'Orders this week: ' + stats[0].total }]675 })676});677 678Response.json({ sent: res.ok });679```680 681- **Schedule config**:682 - Cron: `0 9 * * 1`683 - Timezone: `America/New_York`684 - Config: `{ "recipient": "admin@example.com" }`685 686### Managing Scheduled Functions687 688- **Enable/Disable**: Toggle from the dropdown menu. Disabled schedules won't fire.689- **Edit**: Click Edit to modify the cron expression, linked function, timezone, or config.690- **Delete**: Click Delete to remove (cannot be undone).691- **Status tracking**: Each schedule card shows the next run time, last run status (success/error), and last run time.692 693---694 695## SQL Editor696 697The SQL Editor allows direct SQL query execution against your deployment's database.698 699### Executing Queries700 7011. Go to **Server Settings > SQL**7022. Type your SQL query in the editor7033. Click **Execute** or press `Ctrl/Cmd + Enter`7044. View results in the table below705 706### Query History707 708The editor maintains a history of your last 20 queries (stored in browser localStorage).709 710Click **History** to view and re-run previous queries.711 712### Supported Operations713 714```sql715-- SELECT queries716SELECT * FROM products WHERE price > 100;717SELECT COUNT(*) FROM orders;718 719-- INSERT720INSERT INTO products (name, price) VALUES ('Widget', 29.99);721 722-- UPDATE723UPDATE products SET price = 24.99 WHERE id = 1;724 725-- DELETE726DELETE FROM products WHERE discontinued = 1;727 728-- CREATE TABLE729CREATE TABLE products (730 id INTEGER PRIMARY KEY AUTOINCREMENT,731 name TEXT NOT NULL,732 price REAL DEFAULT 0,733 created_at DATETIME DEFAULT CURRENT_TIMESTAMP734);735 736-- ALTER TABLE737ALTER TABLE products ADD COLUMN stock INTEGER DEFAULT 0;738 739-- DROP TABLE (use with caution!)740DROP TABLE old_products;741```742 743### Query Safety744 745- System tables are accessible (read-only for some operations)746- All queries are executed with full permissions - use caution!747- No automatic transaction management - consider wrapping related operations748 749---750 751## Schema Viewer752 753The Schema Viewer displays your database structure.754 755### Viewing Tables756 7571. Go to **Server Settings > Schema**7582. Click on a table name to expand and view columns7593. Each column shows: name, type, nullable, default value, primary key status760 761### System Tables762 763Toggle **Show System Tables** to view OSW Studio's internal tables:764- `site_info` - Deployment metadata765- `files` - File contents766- `file_tree_nodes` - File tree structure767- `pageviews` / `interactions` / `sessions` - Analytics data768- `edge_functions` - Function definitions769- `function_logs` - Execution logs770 771System tables are marked with a "(system)" label.772 773---774 775## Execution Logs776 777View function execution history in the **Logs** tab.778 779### Log Information780 781Each log entry shows:782- **Status**: Success (2xx), redirect (3xx), or error (4xx/5xx)783- **Function**: Function name784- **Method**: HTTP method used785- **Path**: Request path786- **Duration**: Execution time in milliseconds787- **Time**: Timestamp788 789### Managing Logs790 791- Click **Refresh** to load latest logs792- Click **Clear** to delete all logs (cannot be undone)793- Logs are limited to the most recent 200 entries794 795---796 797## Best Practices798 799### Function Design800 8011. **Keep functions focused** - One function per operation8022. **Validate input** - Check `request.body` and `request.query` before use8033. **Handle errors** - Return appropriate HTTP status codes8044. **Use meaningful names** - `create-order` not `func1`805 806### Database Usage807 8081. **Use parameterized queries** - Prevents SQL injection809 ```javascript810 // Good811 db.query('SELECT * FROM users WHERE id = ?', [userId]);812 813 // Bad - SQL injection risk!814 db.query(`SELECT * FROM users WHERE id = ${userId}`);815 ```816 8172. **Create indexes** for frequently queried columns:818 ```sql819 CREATE INDEX idx_orders_user_id ON orders(user_id);820 ```821 8223. **Limit result sets** to avoid memory issues:823 ```javascript824 db.query('SELECT * FROM products LIMIT 100');825 ```826 827### Performance828 8291. **Set appropriate timeouts** - Don't use 30s if 5s is sufficient8302. **Minimize external requests** - Each `fetch()` adds latency8313. **Cache when possible** - Store API responses in your database832 833---834 835## Troubleshooting836 837### Function Returns 404838- Check that the function is **enabled**839- Verify the function name in the URL is correct840- Ensure the deployment is published and has database enabled841 842### Function Returns 500843- Check the **Logs** tab for error details844- Verify your SQL queries are valid845- Ensure external APIs are responding846 847### Query Execution Failed848- Check SQL syntax849- Verify table and column names850- Look for constraint violations (unique, foreign key)851 852### Cannot Access Table853- System tables are protected in edge functions854- Use the SQL Editor for system table access855 856---857 858## API Reference859 860### Public Endpoints861 862| Method | URL | Description |863|--------|-----|-------------|864| * | `/api/deployments/{deploymentId}/functions/{name}` | Invoke edge function |865| * | `/api/deployments/{deploymentId}/functions/{name}/*` | Invoke with path params |866 867### Admin Endpoints (Requires Authentication)868 869| Method | URL | Description |870|--------|-----|-------------|871| GET | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/functions` | List edge functions |872| POST | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/functions` | Create edge function |873| GET | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/functions/{id}` | Get edge function |874| PUT | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/functions/{id}` | Update edge function |875| DELETE | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/functions/{id}` | Delete edge function |876| GET | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/server-functions` | List server functions |877| POST | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/server-functions` | Create server function |878| GET | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/server-functions/{id}` | Get server function |879| PUT | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/server-functions/{id}` | Update server function |880| DELETE | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/server-functions/{id}` | Delete server function |881| GET | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/scheduled-functions` | List scheduled functions |882| POST | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/scheduled-functions` | Create scheduled function |883| GET | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/scheduled-functions/{id}` | Get scheduled function |884| PUT | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/scheduled-functions/{id}` | Update scheduled function |885| DELETE | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/scheduled-functions/{id}` | Delete scheduled function |886| GET | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/secrets` | List secrets (metadata only) |887| POST | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/secrets` | Create secret |888| GET | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/secrets/{id}` | Get secret (metadata only) |889| PUT | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/secrets/{id}` | Update secret |890| DELETE | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/secrets/{id}` | Delete secret |891| GET | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/database/schema` | Get schema |892| POST | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/database/query` | Execute SQL |893| GET | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/database/logs` | Get logs |894| DELETE | `/api/w/{workspaceId}/admin/deployments/{deploymentId}/database/logs` | Clear logs |895 896---897 898## AI Integration899 900OSW Studio's AI assistant can understand and work with your backend features when you select a deployment in the workspace.901 902### How It Works903 9041. **Project Backend Context** - When the project has Backend Enabled, its edge functions, helpers, secrets, schedules, and schema are mounted automatically in `/.server/`9052. **Optional Deployment Overlay** - Selecting a deployment in the workspace header additionally layers in that deployment's runtime state (published functions, live schema, etc.)9063. **AI Awareness** - The AI receives information about available:907 - Edge functions (endpoints, methods)908 - Database schema (tables, columns)909 - Server functions (helpers)910 - Scheduled functions (cron schedules)911 - Secrets (names only, not values)912 913### The `/.server/` Folder914 915When a deployment is selected, a hidden `/.server/` folder appears in the file explorer containing:916 917| Folder | Contents |918|--------|----------|919| `edge-functions/*.json` | Edge function endpoints |920| `server-functions/*.json` | Helper functions |921| `scheduled-functions/*.json` | Cron schedules |922| `secrets/*.json` | Secret names (not values) |923| `db/schema.sql` | Database schema |924 925These files reflect the project's backend feature state:926- **Schema** (`db/schema.sql`) is read-only — edit schema by running SQL in Project Settings or via the AI's `sqlite3` shell command927- **Functions, helpers, secrets, and schedules** can be created and edited by the AI using shell commands. Changes update the corresponding records in the project's database928 929Files are transient in the sense that they're regenerated from the project's backend state — you don't commit them manually.930 931### Using AI with Backend Features932 933**Example prompts:**934 935```936What tables are in the database?937```938 939```940Create an edge function to list all products941```942 943```944I need an endpoint that validates API keys using the STRIPE_KEY secret945```946 947```948Create a scheduled function to clean up old records every night949```950 951```952Help me design a schema for a blog with posts and comments953```954 955The AI can:956- Read and explain your current schema957- Suggest edge function implementations958- Reference available secrets by name959- Help design database structures960- Debug function issues961 962### Viewing Hidden Files963 964To see the `/.server/` folder:9651. Right-click in the File Explorer9662. Select **Show Hidden Files**9673. Look for the folder with the orange server icon968 969See also: **[Server Mode > Server Context Integration](?doc=server-mode#server-context-integration)**970 