Understanding Gingee Scripts

Gingee executes your backend logic using JavaScript files that live inside your app's secure box folder. For consistency and ease of use, all executable scripts—whether they are handling a live API request, acting as middleware, or performing a one-time setup task—share the same fundamental structure. This guide explains the three types of scripts and the powerful $g object that connects them.

The Consistent Script Pattern

All Gingee scripts, regardless of their purpose, follow this simple and mandatory pattern:

// A script must export a single asynchronous function.
module.exports = async function() {

    // The entire logic is wrapped in a call to the global 'gingee()' function.
    await gingee(async function($g) {

        // Your application code goes here.
        // You use the '$g' object to interact with the world.
        
    });
};

This unified structure ensures that every piece of executable code runs within the same secure, sandboxed environment and receives a properly configured context object ($g).

Types of Scripts in Gingee

While the structure is the same, the purpose of a script and the context it runs in can differ. There are four types of scripts you can create, plus WebSocket handlers (different entry signature) and queue job handlers (same gingee() pattern, $g.queue context).

1. Server Scripts (API Endpoints)

This is the most common type of script. It runs in direct response to an incoming HTTP request from a browser or client.

Example (box/api/users/get.js):

module.exports = async function() {
    await gingee(async ($g) => {
        const userId = $g.request.query.id;
        // ... logic to fetch user from database ...
        $g.response.send({ id: userId, name: 'Alex' });
    });
};

2. Default Include Scripts (Middleware)

These scripts run before every Server Script in your application. They act as middleware.

Example (box/auth_middleware.js):

module.exports = async function() {
    await gingee(async ($g) => {
        const token = $g.request.headers['x-auth-token'];
        if (!isValid(token)) {
            // This ends the request immediately.
            $g.response.send({ error: 'Unauthorized' }, 401);
        }
        // If we don't send a response, execution continues to the next script.
    });
};

3. Startup Scripts (Initialization)

These scripts run once when your application is loaded by the server. They are not tied to any HTTP request.

Example (box/setup/create_schema.js):

module.exports = async function() {
    await gingee(async ($g) => {
        const db = require('db');
        $g.log.info('Checking for Users table...');
        
        const sql = 'CREATE TABLE IF NOT EXISTS "Users" (id SERIAL PRIMARY KEY, email TEXT)';
        await db.execute('main_db', sql);
        
        $g.log.info('Database schema is ready.');
    });
};

4. WebSocket Handlers (Realtime)

Long-lived connections use a different entry signature (no gingee() wrapper required). Configure them in app.json → websockets and grant the websockets permission.

Example (box/realtime/handler.js):

module.exports = async function (socket, ctx) {
  const ws = require('websockets');
  const room = ws.tenantRoom(ctx.query.tenant || 'demo', ctx.query.room || 'lobby');
  socket.join(room);
  socket.send({ type: 'welcome' });
  socket.on('message', (raw) => {
    socket.to(room).send({ echo: raw });
  });
};

See Server Config → websockets and App Structure.

5. Queue Job Handlers (Background work)

Deferred jobs use the same module.exports + gingee() pattern as HTTP scripts. Place handlers under box/jobs/{name}.js (or map names in app.json → queue.jobs). Grant the queue permission; enqueue with require('queue').add(name, payload).

Example (box/jobs/send-welcome.js):

module.exports = async function () {
  await gingee(async ($g) => {
    const { payload, attempt, id } = $g.queue;
    // … do work …
  });
};

See Server Config → queue, App Developer Guide, and Glade Admin.


The $g Object: Full API Reference

The $g object is the heart of the server script API. It provides a simplified and secure facade for interacting with the HTTP request, building a response, logging, and accessing application configuration.

$g.request

An object containing all the details of the incoming HTTP request.

$g.response

An object used to build the outgoing HTTP response. You modify its properties and then call $g.response.send() to send it.

Platform limits ($g.limits, abort signal)

When a server script runs under the engine limits module:

Non-stream scripts that exceed request_timeout_ms receive a platform 504 if they have not yet completed. After startStream(), stream idle and hard timeouts apply instead. Concurrency overloads return 503 before the script runs.

Outbound httpclient calls use limits.outbound_timeout_ms by default and are subject to max_concurrent_outbound. They are also checked against server egress policy (gingee.json → egress, default protected); denied URLs return 403 with code: 'EGRESS_DENIED' (private/loopback/metadata blocked unless you configure exceptions or mode: "off").

Scheduled job context

When a script is invoked by the CRON scheduler (see app.json → schedules), there is no HTTP request. The gingee() middleware still provides $g, with:

Streaming responses (SSE and chunked output)

For long-running or progressive output (for example, require('ai').chatStream(...)), use the streaming helpers on $g.response instead of a single send(). These write to the underlying HTTP response without exposing Node's raw res object to the sandbox.

Example (AI streaming via SSE):

module.exports = async function () {
    await gingee(async ($g) => {
        const ai = require('ai');
        const messages = $g.request.body.messages;

        $g.response.startStream(200, 'text/event-stream; charset=utf-8');
        try {
            for await (const chunk of ai.chatStream({ messages })) {
                if (chunk.done) {
                    $g.response.writeSSE({
                        type: 'done',
                        text: chunk.text,
                        model: chunk.model,
                        provider: chunk.provider,
                        usage: chunk.usage || null
                    });
                } else if (chunk.textDelta) {
                    $g.response.writeSSE({ type: 'delta', textDelta: chunk.textDelta });
                }
            }
        } catch (err) {
            $g.response.writeSSE({ type: 'error', error: err.message });
        } finally {
            $g.response.endStream();
        }
    });
};

Clients typically consume this with fetch() + ReadableStream (POST bodies are not supported by browser EventSource).

$g.log

A direct reference to the apps's logger instance, pre-configured with the request's context.

$g.app

An object containing safe, read-only configuration data for the current application.

NOTE: The $g object will not have the $g.request and $g.response objects for a startup script.