Skip to main content
Most backend logic fits the typed statements, filters and expressions. When it does not, pick the lightest option that does the job, in this order:

Native filters first

A filter chain runs inside the request, with no extra process. This adds 20% and rounds to two places:
Given 120.5, it returns 144.6. Every filter and its arguments are in the authoring reference.

A lambda, for a short JavaScript body

s.lambda runs a JavaScript body as a statement and binds what it returns. Write the body as a function: its parameters are the bindings, so a wrong name is a compile error. A stack variable is $var.name; $input, $env and $auth are there too.
The filters fl.map, fl.filter, fl.reduce, fl.find and their relatives also take a body, with $this (the element), $index and $parent. A filter sends its body once for the whole list, which is cheaper than calling a lambda from inside a loop.
  • Only the body crosses. A const from the surrounding module is undefined inside it. Pass values in with lam.fn(fn, { capture: { ... } }).
  • A throw does not fail the request. The error text comes back as the value with HTTP 200, so check a lambda’s result before you rely on it.
  • No imports. Libraries such as _, axios, jose, luxon and uuid are preloaded globals, along with fetch and crypto.

A body in its own file

Once a body has real structure, put it in a module under xano/lambdas/ and read it with lam.file. The module default-exports one function; its own tsconfig type-checks it, and the file is read as text at build time. lam.file reads the filesystem, so it comes from @xano/sdk/node:
Given [{ qty: 2, price: 10 }, { qty: 12, price: 5 }], it returns 74. Move any helper inside the default export: only its body is sent.

An HTTP call, for a service with an API

s.api.request calls any HTTP API and binds a { request, response } envelope. params is the query string for GET and the body otherwise. Keep credentials in a header read from env():
  • A failed call does not throw. An error status is an ordinary response, and a timeout or DNS failure is response.status 0. Check the status, as the precondition does here.
  • Send a User-Agent. None is sent by default, and some APIs answer 403 without one.
  • A structured body goes in obj({ ... }), which carries values from the stack at any depth.
Declare COMPANY_API_KEY in workspaceConfig({ env }) and keep its value in xano/.env (see Env and secrets).

A microservice, for a container

A microservice() is a container workload deployed alongside the workspace: a long-running process, or code in a language other than JavaScript. A stack calls it with s.microservice.request:
  • Pass the def as host. A rename then fixes every call site, and the port is checked before deploy. A microservice with several servicePorts needs port on the call.
  • Secrets. A container’s env entry with fromEnv names a workspaceConfig({ env }) variable, and Xano fills in the value at deploy. Never write a secret as a literal value, and do not use process.env.X in the def: it is read at export and the value lands in the bundle.
  • A Helm chart is the other shape: kind: "helm" with a chart and its values. Use ${env.NAME} for a secret inside values.
  • Private registries. registryAuth.dockerconfigjson is stored as written, so a bundle that carries one is secret material. Prefer a public image.
The microservice surface is new and may change between releases; the SDK prints a notice on every export that includes one.

Register them

Next