15. Edge Functions
Alex needs to process a payment webhook and update an order — this requires custom server-side logic beyond SQL.
Edge Functions are serverless TypeScript/JavaScript functions that run on Eurobase's EU-sovereign infrastructure. They let you write custom server-side logic — payment processing, external API integrations, data transformations — without managing any servers.
EU Sovereign: Edge Functions run on Scaleway infrastructure in France. Unlike other platforms that route through US-hosted runtimes, your code and secrets never leave the EU.
Server-side logic in Eurobase: four kinds, when to pick which
Eurobase has four distinct surfaces for "code that runs on the server." The SDK shapes look similar in places, so it's worth getting the distinction clear before you start building. RPC functions and DB triggers are both PostgreSQL functions but with different invocation models; Cron jobs are scheduled wrappers around either an SQL statement or an RPC; Edge functions are a separate Deno runtime entirely.
| Cron Job | RPC Function | DB Trigger | Edge Function | |
|---|---|---|---|---|
| What it is | A schedule that runs SQL or an RPC at a cron expression | A reusable Postgres function (SQL/PL-pgSQL) you call by name | A Postgres function bound to a row event on a table | Serverless TS/JS in a Deno container |
| Where it runs | Inside Postgres (cron worker fires the SQL/RPC) | Inside Postgres | Inside Postgres, in the row's transaction | Deno container, outside the database |
| How it's invoked | By the cron schedule (no caller) | eb.db.rpc('name') from the SDK, or from a cron job | Automatically, when an INSERT / UPDATE / DELETE / TRUNCATE happens on the attached table | eb.functions.invoke('name') or HTTP |
| Language | SQL (or pick an RPC) | SQL or PL/pgSQL | PL/pgSQL (return type trigger) | TypeScript / JavaScript |
| Transactional | Yes — its own transaction at run time | Yes — runs in the caller's transaction | Yes — runs in the row operation's transaction (can roll it back by raising) | No — separate process |
| Where to manage it | Cron & RPC → Scheduled Jobs | Cron & RPC → Functions | Function: Cron & RPC → Functions (Returns: trigger). Attachment: Database → table → Triggers panel | Functions tab |
| Use case examples | Daily cleanup of expired sessions; weekly digest aggregation; archiving old rows | Computed leaderboard, multi-statement bulk update, an atomic check-and-decrement, complex aggregate the SDK can call by name | Enforcing a max-N-rows-per-user constraint on INSERT; auto-stamping updated_at; mirroring inserts into an audit table | Stripe / Mollie webhook; sending email via TEM; calling an external image-processing API; OAuth callback handling |
| Pick when | You want something to run on a schedule, regardless of user activity | Your app needs to call a chunk of DB-only logic by name, atomically | A row change must always be accompanied by side-effect SQL — and the side effect must succeed or fail with the row change | You need the JS ecosystem, an external HTTP call, or anything Postgres can't reach from within the database |
Mental model: Cron jobs are scheduled SQL; RPC is callable SQL; DB triggers are reactive SQL; edge functions are everything else. The first three all run inside Postgres and share its transactional model. Edge functions are a separate runtime that talks to the DB over the SDK like your app does.
One subtle gotcha: a function that RETURNS trigger is created in the same place as RPC functions, but it won't appear in the RPC list and can't be called via eb.db.rpc(). It only exists to be attached to a table by a trigger. The Functions list filters it out so the surfaces stay honest. To attach it: Database tab → pick the table → expand the Triggers panel.
Creating a Function
From the console, navigate to Functions tab and click + New Function. Give it a lowercase name with hyphens (e.g., process-order).
Or via CLI:
eurobase edge-functions deploy process-order --file functions/process-order.ts
Function Structure
Write TypeScript or JavaScript and export your handler with export default (or module.exports). The runner passes req (a Request) and ctx (context). Code is compiled on deploy — types are stripped, not checked.
Heads-up: third-party imports (import … from "https://…" or npm packages) are not supported yet — inline dependencies into the function file.
Reading the request (webhooks & APIs)
Your function receives the full incoming request, so it can act as a webhook receiver or small HTTP API:
- Query string —
new URL(req.url).searchParams.get('token') - Custom headers —
req.headers.get('X-Signature'),X-Api-Key,X-Webhook-Id, etc. are forwarded as sent. - Body —
await req.json()/req.text();Content-Typeis preserved.
Withheld from your function so platform credentials don't leak: the auth that authenticates your call to Eurobase — the Authorization, apikey, Cookie headers and the ?apikey= query param — plus Eurobase's internal X-Eurobase-* / X-Project-* / X-Function-* / X-User-* headers. For end-user identity on a verify_jwt function, use ctx.user. Put a partner's auth token in any other custom header (e.g. X-Api-Key, X-Signature) or query param (e.g. ?token=).
TypeScript types (autocomplete + type-checking)
The full req and ctx shapes are shipped from the SDK as a types-only subpath. Import in your function source with a type-only import (bundlers elide it at build time):
import type { EdgeHandler } from '@eurobase/sdk/functions'
const handler: EdgeHandler = async (req, ctx) => {
const { orderId } = (await req.json()) as { orderId: string }
const rows = await ctx.db.sql<{ id: string; total: number }>(
"SELECT id, total FROM orders WHERE id = $1",
[orderId],
)
return Response.json(rows[0])
}
export default handler Requires @eurobase/sdk@0.7.0 or later. The exported types (EdgeContext, EdgeHandler, EdgeUser, EdgeStorageUploadResult, EdgeSignedUrlResult, EdgeLogger) mirror the runtime ctx the runner passes to your handler, so autocomplete and type-checking cover every helper. Note: ctx.db.sql(...) resolves to the bare row array — assign directly with const rows = ..., not destructured with const { rows } = ....
Example handler
module.exports = async (req, ctx) => {
// Parse the incoming request
const { orderId } = await req.json();
// Query the database (scoped to your project)
// ctx.db.sql resolves to the rows array directly.
const rows = await ctx.db.sql(
"SELECT * FROM orders WHERE id = $1",
[orderId]
);
const order = rows[0];
// Read a secret from Vault
const apiKey = await ctx.vault.get("PAYMENT_API_KEY");
// Call an external API
const payment = await fetch("https://api.mollie.com/v2/payments", {
method: "POST",
headers: { Authorization: `Bearer ${ apiKey }` },
body: JSON.stringify({ amount: order.total })
});
// Return a response
return new Response(JSON.stringify({ status: "ok" }), {
status: 200,
headers: { "Content-Type": "application/json" },
});
}; Context API
| Property | Description |
|---|---|
| ctx.db.sql(query, params) | Execute SQL scoped to your project schema |
| ctx.vault.get(name) | Read an encrypted secret from Vault |
| ctx.env | Per-function environment variables |
| ctx.user.id / ctx.user.email | Authenticated user (if JWT required) |
| ctx.log.info(msg) / .warn / .error | Structured logging (visible in Logs) |
Invoking Functions
Functions are invoked via HTTP using your API key:
POST https://your-project.eurobase.app/v1/functions/process-order
Authorization: Bearer <user-jwt>
apikey: eb_pk_...
{"orderId": "abc-123"} Or via the SDK:
const { data, error } = await eurobase.functions.invoke('process-order', {
body: { orderId: 'abc-123' },
}); CLI Commands
# List edge functions eurobase edge-functions list # Deploy from local file eurobase edge-functions deploy process-order -f functions/process-order.ts # View execution logs eurobase edge-functions logs process-order # Delete a function eurobase edge-functions delete process-order
Plan Limits
| Limit | Free | Pro |
|---|---|---|
| Functions per project | 3 | 25 |
| Execution timeout | 10 seconds | 60 seconds |
| Memory per execution | 64 MB | 256 MB |
Use Cases
- Payment webhooks — Process Mollie callbacks, update order status
- External integrations — Sync data to/from other EU SaaS
- Custom auth logic — Post-signup hooks, role assignment
- Data transformation — Parse CSVs, enrich records, generate reports
- Notifications — Send emails, push notifications on events