Backend Functions
Backend functions allow you to execute server-side logic from your Sp00ky application. Currently, Sp00ky supports backend integration through the outbox pattern, where client operations create job records that are processed asynchronously by a backend service.
How It Works
The outbox pattern decouples your frontend from backend services:
- Your client calls
db.run()to create a job record in SurrealDB - A separate job runner service polls for pending jobs
- The job runner executes the HTTP request to your backend API
- The job status is updated in the database
- Your client can query the job status reactively
Direct backend calls may be supported in future versions. For now, the outbox pattern is the recommended approach for all backend integrations.
Configuration
Backend functions are configured in your sp00ky.yml file in the schema directory.
Configuration Fields
type: The backend type (currently onlyhttpis supported)baseUrl: The base URL of your HTTP backend servicespec: Path to the OpenAPI specification file that defines your API routesauth(optional): Authentication configuration for the backendtype: Authentication type (currently onlytokenis supported)token: The bearer token to include in HTTP requests
method.type: The method for calling the backend (useoutboxfor the outbox pattern)method.table: The name of the job table in SurrealDB (e.g.,job)method.schema: Path to the SurrealQL schema file that defines the job table structuredev(optional): A dev server command thatspooky devwill spawn alongside infrastructure (see Dev Server below)
Authentication
When you configure an auth block in your backend configuration, the job runner will automatically include the token in all HTTP requests to that backend using the Authorization: Bearer <token> header.
This is useful for securing your backend APIs and ensuring that only authorized job runners can execute requests.
On your backend server, validate the bearer token in your middleware:
OpenAPI Specification
Your backend routes are defined using an OpenAPI specification. Each path becomes a callable route from your Sp00ky client.
The request body schema defines the parameters that will be validated when you call db.run(). In this example, the /spookify endpoint requires an id parameter.
Job Table Schema
The job table stores outbox records and tracks their execution status. Here’s the required schema:
Key Fields
assigned_to: Links the job to a parent record (optional, but useful for querying jobs by entity)path: The API route path from your OpenAPI specpayload: JSON payload containing the request parametersstatus: Current job state:pending,processing,success, orfailedretries: Number of retry attempts so farmax_retries: Maximum number of retry attempts before marking as failedretry_strategy: Retry timing strategy (linearorexponential)errors: Array of error objects from failed execution attempts
Calling Backend Functions
Use the db.run() method to create a job and call your backend function:
Method Signature
Parameters
backend: The name of the backend from yoursp00ky.yml(e.g.,'api')path: The API route path (e.g.,'/spookify')payload: An object containing the request parameters defined in your OpenAPI schemaoptions(optional):assignedTo: Record ID to link this job to (useful for querying related jobs)max_retries: Override the default maximum retry attempts (default: 3)retry_strategy: Override the retry strategy (default:'linear')
The db.run() method validates the payload against your OpenAPI schema. Missing required parameters will throw an error.
Querying Job Status
Jobs are stored as regular SurrealDB records, so you can query them using the query builder. The most common pattern is to use .related() to fetch jobs for a specific entity:
This pattern is reactive - when the job runner updates the job status in the database, your UI will automatically reflect the changes.
Complete Example
Here’s a complete example of implementing a “spookify” feature that generates AI content:
Job Runner Setup
The job runner is a separate service that processes outbox jobs. You can use the example job runner from the Sp00ky repository or implement your own.
The job runner:
- Polls the job table for records with
status = 'pending' - Updates status to
'processing' - Makes the HTTP request to your backend
- Updates status to
'success'or'failed' - Implements retry logic based on
retry_strategy
See the packages/job-runner directory in the Sp00ky repository for a reference implementation in Rust.
Dev Server
Each backend in sp00ky.yml can declare an optional dev field. When you run spooky dev, these commands are spawned alongside the rest of the infrastructure (SurrealDB, SSP, scheduler), so you don’t need to start backend services in separate terminals.
Each backend gets a unique color in the terminal and its output is prefixed with [backend.<name>.dev], making it easy to distinguish logs from different services.
String Form
The simplest option is a raw shell command:
This runs the command from the project root using sh -c.
npm Type
Run an npm/pnpm script:
Docker Type
Build and run a Dockerfile:
uv Type (Python)
Run a Python script via uv:
This uses uv run to execute the script inside the project’s virtual environment, automatically installing dependencies from pyproject.toml if needed.
env-file
All typed forms support an optional env-file field that loads environment variables from a dotenv-style file (one KEY=VALUE per line, # comments and blank lines are ignored). The path is resolved relative to the project root.
For the docker type, the file is passed directly via Docker’s --env-file flag. For all other types (npm, uv), the variables are parsed and injected into the spawned process environment.
workdir
All typed forms support an optional workdir field that is resolved relative to the project root (the directory containing sp00ky.yml). If omitted, commands run from the project root.
Backend dev processes are automatically stopped when you press Ctrl+C to shut down spooky dev.
Best Practices
Use assignedTo for Related Jobs
Always link jobs to their parent entity using assignedTo. This makes it easy to query related jobs:
Handle Failed Jobs in Your UI
Check the job status and provide feedback to users:
Set Appropriate Retry Policies
For idempotent operations, use higher retry counts. For non-idempotent operations, use lower retry counts or disable retries:
Use Permissions to Secure Jobs
The job table should have permissions that prevent users from seeing or modifying other users’ jobs:
This ensures users can only access jobs assigned to records they own.