Groundfloor Docs

Node.js handler reference

Handler contract for Coderunner Node.js functions, background jobs, and scheduled jobs.

Applies to the function, job, and schedule workload types — code you upload as a ZIP and Code Runner executes against a Node bootstrap image. For a service (any container, your own Dockerfile), see Deploy a container image instead — there's no handler contract there, just a container listening on a port.

Project structure

my-coderunner/
├── handler.js
└── package.json

package.json must declare "scripts": { "start": "node handler.js" } — the bootstrap image runs CMD ["npm", "start"]. A missing start script crash-loops the container and the deployment fails.

Handler contract

const handler = async (context, payload) => {
  // ...
};

module.exports = { handler };
TypeSignatureReturn value
functionasync (context, payload){ statusCode, body, headers } — required. A function is a request/response HTTP handler; whatever you return becomes the response.
job / scheduleasync (context, payload)None required — the process runs to completion and exits. Log via console.log/console.error; there's no response to return.

payload is the JSON body supplied by the caller (function) or trigger (job/schedule, e.g. a cron firing). context is runtime-provided; no properties are documented yet beyond its presence in the signature — don't depend on undocumented context fields.

Minimal example (function)

const handler = async (context, payload) => {
  try {
    if (!payload?.name) {
      return {
        statusCode: 400,
        body: { error: "Name is required" },
        headers: { "content-type": "application/json" },
      };
    }
    return {
      statusCode: 200,
      body: { message: `Hello ${payload.name}` },
      headers: { "content-type": "application/json" },
    };
  } catch (err) {
    console.error("Function failed", err);
    return {
      statusCode: 500,
      body: { error: "Internal server error" },
      headers: { "content-type": "application/json" },
    };
  }
};

module.exports = { handler };

A job/schedule handler looks the same minus the return — do the work, log, and let the function end:

const handler = async (context, payload) => {
  try {
    console.log("Background job started");
    await doSomething(payload);
    console.log("Background job completed successfully");
  } catch (err) {
    console.error("Background job failed", err);
  }
};

module.exports = { handler };

External packages

Declare npm dependencies in package.json as usual; they're installed as part of the build.

{
  "name": "coderunner-function",
  "version": "1.0.0",
  "main": "handler.js",
  "scripts": { "start": "node handler.js" },
  "dependencies": { "axios": "^1.7.0" }
}

fetch() is available without an extra dependency for outbound HTTP calls.

Environment variables and secrets

process.env.<NAME> reads deploy-time env vars (gf deploy -e KEY=value) and injected secrets (gf deploy --secret KEY, see Secrets). Never hard-code API keys, passwords, or connection strings — and never zip a .env file.

Common status codes (function)

CodeUsage
200Successful execution
201Resource created
400Invalid or missing input
401 / 403Auth required / denied
404Not found
500Unexpected error — don't leak exception internals in the body

Best practices

  1. Always export via module.exports = { handler }.
  2. Use async/await; check response.ok on outbound fetch() calls.
  3. Validate payload before using it — it's caller-supplied.
  4. Catch and log errors; don't let an unhandled rejection crash-loop the container.
  5. Keep secrets out of the ZIP — inject them at deploy time.

On this page