Custom server code
Most apps read and write data through the built-in hooks, but sometimes you need code that runs on the server: logic that uses a secret key, talks to a third-party API, or enforces rules the client should not control. Instroc gives you two ways to do that, API route files inside your project and serverless functions in the Cloud panel. This page covers both.
You can write these yourself in the code editor, or simply describe what you need and let Roc write them ("add an endpoint that returns each user's order total").
API route files
Any file under the /api/ folder in your project becomes a server endpoint on your app. The file path decides the URL:
/api/users.tshandles requests to/api/users/api/posts/[id].tshandles/api/posts/123, with123available as a route param
Inside the file, you export an async function named after each HTTP method you want to support: GET, POST, PUT, or DELETE. Each handler receives a single ctx object with everything it needs.
// /api/posts/[id].ts
export async function GET(ctx) {
const { data } = await ctx.db.query('posts', {
filter: { id: ctx.params.id },
});
if (data.length === 0) {
return new Response('Not found', { status: 404 });
}
return { post: data[0] };
}
Returning a plain object sends it as JSON automatically. Returning a Response gives you full control over status codes and headers.
The ctx object
Every handler gets the same context:
| Property | What it is |
|---|---|
request | The incoming request (method, headers, body) |
params | Route params from [bracket] segments in the file path |
user | The signed-in user ({ id, email, isOwner }) or null, taken from the secure session |
db | Database access: query, insert, update, delete, rawQuery, aggregate, batch |
storage | File storage: upload and list |
env | Your project's environment variables and secrets |
url | The full request URL, including query parameters |
Because ctx.user comes from the session cookie, you can trust it. A common pattern is gating an endpoint to signed-in users or to you as the owner:
// /api/orders.ts
export async function POST(ctx) {
if (!ctx.user) {
return new Response('Unauthorized', { status: 401 });
}
const body = await ctx.request.json();
const { data } = await ctx.db.insert('orders', {
item: body.item,
quantity: body.quantity,
});
return { order: data[0] };
}
Never pass id, created_at, or updated_at to an insert; the database generates them. For bulk work, ctx.db.batch handles up to 100 items per call.
Calling routes from your frontend
Your app calls its own routes with plain fetch, and the session travels along automatically:
const res = await fetch('/api/orders', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ item: 'Poster', quantity: 2 }),
});
const { order } = await res.json();
API routes or data hooks?
For ordinary reads and writes, the data hooks (useQuery, useMutation) are simpler and already secured by your tables' visibility settings, so prefer them. Reach for an API route when you need:
- Server-side logic, like validating a discount code or computing something the client should not see or change
- Secrets, since
ctx.envis only available on the server and keys never reach the browser - Third-party calls, so external APIs are called from your backend rather than from users' browsers
See the Data API reference for the hooks and Security rules for table visibility.
Serverless functions
Alongside route files, the Cloud panel has a Functions section for standalone server functions. Three ready-made templates cover the common cases: send_email (outbound email through a connected email integration), process_payment (payments through your connected Stripe account), and ai_call (calls to an AI integration you have connected). See Integrations for connecting those services.
You can also create fully custom functions, each with one of three triggers:
- HTTP: the function runs when your app invokes it
- Scheduled: the function runs on a cron-style timer, managed in the Scheduled Tasks panel (nightly cleanups, weekly digests)
- Webhook: the function runs when an external service posts to its endpoint, covered in Webhooks
Your frontend invokes functions through the functions hook, documented in the Functions API reference.
Rules to remember
API routes and functions are server-side only. They cannot import UI components or anything from your app's pages, and nothing in them runs in the browser. Keep them focused on data, logic, and external calls, and keep secrets in Cloud environment variables (API & Secrets panel) rather than in the code itself. See Environment variables.