Skip to main content

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.ts handles requests to /api/users
  • /api/posts/[id].ts handles /api/posts/123, with 123 available 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:

PropertyWhat it is
requestThe incoming request (method, headers, body)
paramsRoute params from [bracket] segments in the file path
userThe signed-in user ({ id, email, isOwner }) or null, taken from the secure session
dbDatabase access: query, insert, update, delete, rawQuery, aggregate, batch
storageFile storage: upload and list
envYour project's environment variables and secrets
urlThe 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] };
}
Good to know

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.env is 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.