Anatomy of a Gingee App

Every application built on Gingee follows a simple and consistent structure. This guide breaks down that structure, explains the critical role of the box folder, and provides a comprehensive reference for all the settings available in the app.json and routes.json configuration file.

App Folder Structure

Every first-level directory inside your server's web root is considered a distinct App. For an application named my_app, the structure looks like this:

web/
└── my_app/
    β”œβ”€β”€ box/
    β”œβ”€β”€ css/
    β”œβ”€β”€ images/
    β”œβ”€β”€ scripts/
    └── index.html

The Importance of the box Folder

The box folder is the private, secure core of your application. It contains all your backend logic, configuration, and private data.


The app.json File:

The app.json file, located at web/my_app/box/app.json, is the central configuration file for your application. It tells the Gingee server how to handle the app, what resources it needs, and how it should behave.

Here is a comprehensive breakdown of all available properties.

{
  "name": "My Awesome App",
  "description": "This is a demonstration of all app.json settings.",
  "version": "1.2.0",
  "type": "MPA",
  "mode": "production",
  "spa": {
    "enabled": false,
    "dev_server_proxy": "http://localhost:5173",
    "build_path": "./dist",
    "fallback_path": "index.html"
  },
  "db": [],
  "email": {
    "type": "console",
    "from": "noreply@example.com",
    "from_name": "My App"
  },
  "ai": {
    "type": "mock",
    "default_model": "mock-model"
  },
  "schedules": [],
  "startup_scripts": [],
  "default_include": [],
  "env": {},
  "jwt_secret": "a-very-strong-and-unique-secret-key",
  "cache": {
    "client": {
      "enabled": true,
      "no_cache_regex": ["/api/realtime"]
    },
    "server": {
      "enabled": true,
      "no_cache_regex": ["/api/dynamic-script.js"]
    }
  }
}

Core Metadata

Application Type & Mode

SPA Configuration (spa object)

This object is used when the app is of "type": "SPA". SPA behavior is active when both "type": "SPA" and "spa.enabled": true are set.

Database Connections

AI (ai object, optional)

Single generative AI configuration for the app. App config overrides optional server defaults in gingee.json β†’ ai. Requires the ai permission.

API (sandbox): require('ai') β†’ chat, chatStream (async generator), complete, parseDocument, moderate. Pass { config: { … } } as the second argument to override server/app config for one call.

Example:

"ai": {
  "type": "gemini",
  "api_key": "AIza…",
  "default_model": "gemini-2.5-pro"
}

Secrets in app.json

Any string value may be a secret reference resolved by the engine at app load (not by sandbox process.env):

"jwt_secret": "env:GINGEE_MYAPP_JWT_SECRET",
"db": [{
  "type": "postgres",
  "name": "main",
  "host": "db.internal",
  "user": "myapp",
  "password": "env:GINGEE_MYAPP_DB_PASSWORD",
  "database": "myapp"
}],
"email": {
  "type": "sendgrid",
  "api_key": "env:GINGEE_MYAPP_SENDGRID_KEY",
  "from": "noreply@example.com"
},
"ai": {
  "type": "gemini",
  "api_key": "file:./settings/secrets/myapp_gemini_key"
}

App scripts never need host process access; resolved values appear on $g.app / module config as normal strings. Server settings: Server Config β†’ secrets.

Limits (limits object, optional)

Optional tightening of server gingee.json β†’ limits for this app only (cannot raise ceilings).

"limits": {
  "request_timeout_ms": 15000,
  "max_concurrent_requests": 10,
  "outbound_timeout_ms": 8000
}

See Server Config for full field list and defaults. Use this to protect a noisy app from monopolizing the process (lower concurrency) or to fail faster than the server default.

Queue (queue object, optional)

Optional job name β†’ script map for the background queue. Handlers default to box/jobs/{name}.js if unmapped. Requires the queue permission and server queue.enabled (default true).

"queue": {
  "jobs": {
    "send-welcome": { "script": "jobs/send_welcome.js" }
  }
}

Enqueue from scripts: require('queue').add('send-welcome', payload). Exhausted retries β†’ DLQ; operators use Glade β†’ Queue / DLQ (live jobs + retry/discard). See Server Config β†’ queue.

WebSockets (websockets object, optional)

Opt-in WebSocket endpoint for this app. Requires server websockets.enabled (default true), the websockets permission, and a handler under box/. Connections use the public HTTP(S) port: ws://host/{appFolder}{path}.

"websockets": {
  "enabled": true,
  "path": "/ws",
  "handler": "realtime/handler.js",
  "auth": "realtime/auth.js",
  "allowed_origins": ["https://example.com"]
}

Handler (box/realtime/handler.js):

module.exports = async function (socket, ctx) {
  // ctx: { app, log, query, path, headers, meta, remoteAddress }
  socket.join('lobby');
  socket.send({ type: 'hello' });
  socket.on('message', (raw) => {
    socket.to('lobby').send({ echo: raw });
  });
};

From HTTP scripts: require('websockets').toRoom('lobby', payload). Multi-tenant: use tenantRoom(tenantId, 'lobby'). Multi-node masters: operator sets websockets.fanout.driver: "redis" + sibling websockets.redis. Sample: web/ginchat/. Full server keys: Server Config β†’ websockets.

Isolation (isolation string, optional)

Opt-in process isolation for this app’s server scripts (not static files). Only takes effect when the server has gingee.json β†’ isolation.mode: "process". Privileged apps (e.g. Glade) always stay in-process regardless of this flag.

Value Meaning
"process" Run box scripts in a solo child worker (app:<folderName>) unless the app is also in a server group
"inprocess" Force in-process (default when server mode is process but app is unmarked)
"isolation": "process"

Server-side alternatives (no need to set this flag if you use them):

Buffered responses and SSE (startStream / writeSSE / endStream) are supported over IPC. Full server keys: Server Config β†’ isolation.

Schedules (schedules array, optional)

Declarative CRON jobs for this app. Registered only when gingee.json β†’ scheduler.enabled is true on this node (default false; optional multi-node Redis coordination). The app must be granted the scheduler permission. URL targets also require httpclient. Queue targets also require queue. Operators can Run now from Glade Schedules.

Each entry:

Field Required Description
name yes Unique job id within the app (a-zA-Z0-9._-)
cron yes CRON expression (standard 5-field; seconds supported by engine dialect)
timezone no IANA timezone (defaults to server scheduler.timezone, usually UTC)
enabled no Default true. Set false to keep the definition without registering
timeout_ms no Default 300000 (script) / 60000 (url) / 30000 (queue enqueue)
overlap no Only "skip" in v1 (skip if previous run still active)
payload no Passed as $g.request.body for script targets; default payload for queue targets
target yes See below

target for scripts (path is relative to the app’s box/ folder only):

"target": { "type": "script", "path": "jobs/nightly_cleanup.js" }

Scheduled scripts run in the same sandbox as HTTP/startup scripts. Use the usual gingee(async ($g) => { … }) form. There is no HTTP connection: $g.request.method is "SCHEDULE", $g.schedule holds { name, cron, timezone, runId, scheduledAt, … }, and $g.response.send(...) records a result in logs (it does not open a network response). Streaming is not supported in schedule context.

fs paths in scheduled scripts: Same rules as all Gingee scripts. A path with a leading / is relative to the scope root (box/ or web/). A path without a leading slash is relative to the executing script’s directory. Example: from box/jobs/cleanup.js, fs.writeFile(fs.BOX, 'data/out.json', …) writes box/jobs/data/out.json, while fs.writeFile(fs.BOX, '/data/out.json', …) writes box/data/out.json. Prefer leading-/ paths when another HTTP script (with a different working directory) must read the same file.

target for external URLs:

"target": {
  "type": "url",
  "url": "https://partner.example.com/hooks/tick",
  "method": "POST",
  "headers": { "Authorization": "Bearer …" },
  "body": { "source": "gingee" }
}

url must be absolute http: or https:. The engine performs the outbound call (app needs httpclient). URLs are checked against server egress policy at registration and again when the job fires (default protected mode blocks private/loopback/metadata). See Server Config β†’ egress.

target for queue (enqueue a background job β€” multi-node safe when queue.driver is redis):

"target": { "type": "queue", "job": "nightly_cleanup", "payload": { "mode": "full" } }

The schedule only enqueues; the queue worker runs box/jobs/nightly_cleanup.js (or a mapped script). App needs queue + scheduler.

Example:

"schedules": [
  {
    "name": "nightly_cleanup",
    "cron": "0 2 * * *",
    "timezone": "UTC",
    "payload": { "mode": "full" },
    "target": { "type": "script", "path": "jobs/cleanup.js" }
  },
  {
    "name": "nightly_via_queue",
    "cron": "0 3 * * *",
    "target": { "type": "queue", "job": "nightly_cleanup" }
  },
  {
    "name": "partner_ping",
    "cron": "*/15 * * * *",
    "target": {
      "type": "url",
      "url": "https://partner.example.com/hooks/gingee",
      "method": "POST"
    }
  }
]

Email (email object, optional)

Single outbound email configuration for the app (no named profiles). App config overrides optional server defaults in gingee.json β†’ email. Requires the email permission.

Runtime override: from a server script you can call email.sendWithConfig(config, message) so a one-off send uses config that overrides both gingee.json and app.json for that transaction only (does not change the app default).

Example app.json:

"email": {
  "type": "sendgrid",
  "api_key": "SG.xxxxx",
  "from": "noreply@example.com",
  "from_name": "My App"
}

Script Execution Configuration

Cache


The pmft.json File (Permissions Manifest)

If you plan to distribute your application as a .gin package, you must declare the permissions it requires in a pmft.json file. This manifest is read by the gingee-cli during the installation process to request consent from the server administrator.

For a complete guide on the permissions system and the structure of this file, please see the Gingee Permissions Guide MD HTML.


The routes.json File (Manifest-Based Routing)

For applications that require more powerful and flexible routing, such as RESTful APIs with dynamic path parameters, you can create a routes.json file. When this file is present in an app's box folder, it activates manifest-based routing, which takes precedence over the default file-based routing.

Structure of routes.json

The file must contain a single root object with a routes key, which holds an array of route definition objects.

{
  "routes": [
    {
      "path": "/users",
      "method": "GET",
      "script": "users/list.js"
    },
    {
      "path": "/users/:userId",
      "method": "GET",
      "script": "users/get.js"
    },
    {
      "path": "/users/:userId",
      "method": "PUT",
      "script": "users/update.js"
    },
    {
      "path": "/:category/:slug/images/:imageId?",
      "method": "GET",
      "script": "content/view.js"
    }
  ]
}

Route Definition Properties

Each object in the routes array defines a single endpoint and has the following properties:

Accessing Path Parameters

When a route with dynamic parameters is matched, Gingee automatically parses the values from the URL and makes them available in your server script via the $g.request.params object.

Example: