npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

cordovabox

v2.3.8

Published

CordovaBox is a Node.js MVC framework and CLI for scaffolding Express-style applications with controllers, models, routes, hooks, asset builds, Socket.io, Redis-backed sessions, and MongoDB/Waterline data layers.

Readme

CordovaBox

CordovaBox is a Node.js MVC framework and CLI for scaffolding Express-style applications with controllers, models, routes, hooks, asset builds, Socket.io, Redis-backed sessions, and MongoDB/Waterline data layers.

Quick Start

Install the CLI and create an app:

npm install -g cordovabox
cordovabox new MyProject
cd MyProject
npm install
cordovabox dev

Open http://localhost:3000.

Official Documentation

The human and AI readable documentation lives in docs/README.md, with a dark-default static docs site at docs/index.html and one HTML page per guide under docs/html/. Generated scaffold apps include a homepage link to /docs/ after the normal asset build.

CLI Commands

| Command | Purpose | | --- | --- | | cordovabox -v | Print the installed CordovaBox version. | | cordovabox new <appname> | Copy the bundled app template into ./<appname> and set the generated package.json name. | | cordovabox generate crud <name> --print | Print the generated CRUD controller, model, service, authenticated route config, and policy source to stdout. | | cordovabox generate crud <name> --write | Write generated CRUD files into the current app without overwriting existing files. Add --force to replace existing files. | | cordovabox generate-api <name> | Create an API bundle: controller, model, service, authenticated /api/<name> routes, and a default isAuthenticated policy if missing. Use --route-base /api/custom to override the route path. | | cordovabox generate-route <name> | Add singular REST routes for api/controllers/<name>Controller.js and api/models/<name>.js under /api/<name>. Use --route-base /api/custom to override the route path. | | cordovabox generate-model <name> | Create only api/models/<name>.js; the model is exposed globally after startup. | | cordovabox generate-controller <name> | Create only api/controllers/<name>Controller.js with editable CRUD actions. | | cordovabox generate-service <name> | Create only api/services/<name>.js; the service is exposed globally as <name>Service after startup. | | cordovabox generate-policy <name> | Create api/policies/httpRequests/<name>.js for use in config/routes.js policies. | | cordovabox start | Start the app on PORT or port 3000. | | cordovabox start <port> | Start the app on a specific port. | | cordovabox dev | Run the generated app's development build/watch tasks and start the server. |

When working from this repository instead of a global install, create a generated app first:

node bin/index.js -v
node bin/index.js new MyProject
cd MyProject
npm install
node ../bin/index.js generate-api user
node ../bin/index.js start 3000

Generated App Workflow

Run generator commands from the generated app directory because paths are relative to the current working directory.

cordovabox new MyProject
cd MyProject
npm install
cordovabox generate-api user
cordovabox dev

generate-api user creates:

api/controllers/userController.js
api/models/user.js
api/services/user.js
api/policies/httpRequests/isAuthenticated.js
config/routes.js entries with policies: ['isAuthenticated']

It also appends authenticated singular REST routes to config/routes.js:

GET    /api/user
POST   /api/user
GET    /api/user/:id
PUT    /api/user/:id
DELETE /api/user/:id

Use generate-route user to add or refresh only those route entries. The generator keeps singular paths by default. Pass --route-base when the public route should differ from the model name:

cordovabox generate crud user --print
cordovabox generate crud user --write
cordovabox generate crud user --write --force
cordovabox generate-api account --route-base /api/accounts
cordovabox generate-route account --route-base /api/accounts

The generated controller delegates CRUD operations to the generated service, and the route entries are explicit JavaScript config so app developers can audit and edit the policy boundary. --write skips existing controller, model, service, and policy files unless --force is present.

Generated models, services, and controllers are loaded as globals after CordovaBox bootstraps the app. Runtime app code can use them directly, for example let user = await User.find({ username: "derrek" }); or await userService.find(...). Do not add fallback loaders such as global["AudioRegionService"] || require("../services/AudioRegion") inside controllers, services, policies, hooks, or configured route handlers.

New generated apps also include a homepage newsletter example. POST /app/newsletter/subscribe records valid signups in the local Newsletter model first, then optionally syncs to Mailchimp when config/config.js includes thirdParty.email.mailchimp.apiKey, serverPrefix, and listId. The homepage links to /admin/models so local records can be inspected.

Generated apps expose runtime API documentation in development:

GET /admin/docs
GET /admin/docs/openapi.json

The apiDocs hook builds OpenAPI 3.1 output from config/routes.js, loaded model attributes, route policies, generated CRUD resources, legacy model CRUD routes when enabled, and core endpoints such as CSRF, uploads, scoped uploads, and server logs. It loads after adminAuth, so the default /admin/docs route follows configured admin.policies. Disable it with apiDocs.enabled: false or add apiDocs.policies for additional route-local protection.

generate-policy isAuthenticated creates an app/API route policy:

api/policies/httpRequests/isAuthenticated.js

Reference route policies by filename in config/routes.js:

{
    route: '/api/user',
    method: 'get',
    controller: 'UserController',
    model: 'User',
    policies: ['isAuthenticated']
}

Policies are standard Express middleware loaded from api/policies/httpRequests. They can reject a request or attach request state before the controller runs:

// api/policies/httpRequests/isAuthenticated.js
module.exports = function isAuthenticated(req, res, next) {
    var userId = req.userId
        || (req.user && (req.user.id || req.user._id))
        || (req.session && req.session.userId);

    if (!userId) {
        return res.status(401).json({ error: 'Authentication required' });
    }

    req.userId = userId;
    return next();
};

CordovaBox registers a built-in isAuthenticated fallback in the policies hook so apps can opt into API protection even when an older app has not created an app-local policy file. App-local api/policies/httpRequests/isAuthenticated.js overrides the built-in.

By default, configured /api/* routes stay open for first stand-up. Set security.api.protectRoutes: true or legacy api.protectRoutes: true before _Config.api.policies, security.api.defaultPolicies, or ['isAuthenticated'] are applied as defaults. Mark intentional public API exceptions in config/routes.js:

{
    route: '/api/login',
    method: 'post',
    controller: 'AuthController',
    action: 'login',
    public: true,
    policies: []
}

You can also list public exceptions in config/config.js:

api: {
    protectRoutes: true,
    policies: ['isAuthenticated'],
    publicRoutes: ['POST /api/login', 'GET /api/health']
}

API protection is opt-in when api.protectRoutes / security.api.protectRoutes is missing. New scaffold apps start open for first stand-up, then apps can enable protection and keep login or health routes public with security.api.public.routes or legacy api.publicRoutes.

Migration note: generated apps keep legacy /<model> auto CRUD routes enabled for compatibility, but those runtime routes use models.autoCrudPolicies or ['isAuthenticated'] by default unless marked public: true. Prefer explicit /api/<model> routes for new APIs because they are easier to audit. Set models.autoCrudRoutes = false after adding explicit routes if an app wants to remove the legacy /<model> surface.

Routes that need signature verification can opt into raw or text body parsing without changing normal JSON/form parsing. The rawBody hook reads these route settings and mounts the matching parser before routes run:

{
    route: '/webhooks/provider',
    method: 'post',
    controller: 'WebhookController',
    action: 'receive',
    rawBody: {
        type: 'application/json',
        limit: '2mb'
    }
}

Raw-body routes receive req.body as a Buffer and also get req.rawBody set to the same bytes. The shorthand rawBody: true uses type: '*/*' and limit: '1mb'. Routes without rawBody continue to receive parsed JSON/form bodies from the bodyParser hook.

// api/controllers/WebhookController.js
module.exports = {
    receive: async function(req, res) {
        const rawPayload = req.rawBody.toString('utf8');
        // Verify the provider signature against rawPayload here.
        return res.ok({ received: true });
    }
};

For text bodies, use:

{
    route: '/webhooks/text',
    method: 'post',
    controller: 'WebhookController',
    action: 'receive',
    textBody: {
        type: '*/*',
        limit: '1mb'
    }
}

Text-body routes receive req.body as a string.

Sessions And Request IPs

Generated apps keep Express sessions enabled by default for backward compatibility. The session hook owns cookie-parser and express-session; JWT-only apps can disable sessions so cacheable pages do not emit a connect.sid cookie:

server: {
    sessions: {
        enabled: false
    }
}

Apps that use sessions should set a real secret in config or with SESSION_SECRET. The redis hook exposes shared Redis clients on app.redis; Redis-backed sessions can use the legacy flag or the newer store shape:

server: {
    sessions: {
        enabled: true
    },
    session: {
        secret: process.env.SESSION_SECRET,
        store: 'redis'
    }
}

server.redisSessions = true still works. The session hook uses the Redis hook for the connect-redis store. The ipaddress hook stores the requester address on req.ipAddress; it only mirrors to req.session.ipAddress when a session already exists.

Server Logs

CordovaBox keeps an in-memory tail of logs written through the framework logger and exposes a hook-owned viewer at /admin/logs. This is a lightweight watchdog/Papertrail-style runtime panel for local and admin use.

serverLogger: {
    enabled: true,
    route: '/admin/logs',
    maxEntries: 1000,
    socketRoom: 'resource:serverLogger'
}

The page loads CordovaBoxClient, opens the /admin/logs route-shaped socket channel, and receives new entries as log.created envelopes. GET /admin/logs.json is still available for initial page render, manual refresh, filtering, and scripts.

Useful endpoints:

GET /admin/logs       HTML log viewer
GET /admin/logs.json  JSON log tail with optional search, level/levels, direction, and limit query params
POST /admin/logs      Add a piped log entry with { level, message, source, meta }
DELETE /admin/logs    Clear the in-memory tail

Admin Login

CordovaBox ships an opt-in admin auth boundary. The adminAuth hook owns /admin/login and /admin/logout, then applies configured HTTP policies to everything registered later under /admin, including /admin/models and /admin/logs.

Generated apps leave admin tools open by default for backward compatibility:

admin: {
    routePrefix: '/admin',
    policies: [],
    auth: {
        enabled: true,
        loginRoute: '/admin/login',
        logoutRoute: '/admin/logout',
        successRedirect: '/admin/models',
        username: process.env.CORDOVABOX_ADMIN_USERNAME || 'admin',
        password: process.env.CORDOVABOX_ADMIN_PASSWORD || ''
    }
}

Enable the login boundary by setting a password and adding a policy:

admin: {
    routePrefix: '/admin',
    policies: ['isAdminAuthenticated'],
    auth: {
        password: process.env.CORDOVABOX_ADMIN_PASSWORD
    }
}

The admin boundary uses HTTP policies from api/policies/httpRequests; it does not hard-code a session check into /admin itself. The scaffolded isAdminAuthenticated policy is for CordovaBox admin hook pages such as /admin/models and /admin/logs. It accepts the built-in session login flag or admin.auth.validateRequest(req) / admin.auth.isAuthenticated(req) when configured. Admin browser requests that fail the policy redirect to the configured admin.auth.loginRoute.

Keep isAuthenticated for app/API routes. Do not reuse it for CordovaBox admin browser pages unless the app intentionally wants one shared policy.

JWT-only apps can keep the same boundary by disabling the built-in login routes and validating bearer tokens in app-owned request auth:

admin: {
    routePrefix: '/admin',
    policies: ['isAdminAuthenticated'],
    auth: {
        enabled: false,
        validateRequest: function(req) {
            var header = req.get('authorization') || '';
            var token = header.replace(/^Bearer\s+/i, '');
            return token === process.env.CORDOVABOX_ADMIN_TOKEN;
        }
    }
}

Socket policies remain separate. api/policies/sockets/isTokenAuthenticated.js authenticates Socket.IO handshakes and route-channel requests; it is not imported by the admin or log hooks.

Uploaded files are served by the upload hook. New apps keep the default /uploads static mount and can add app-specific mounts in config/config.js:

uploads: {
    mounts: [
        { urlPath: '/uploads', directory: 'uploads' },
        { urlPath: '/message-uploads', directory: 'uploads/messages' }
    ],
    builtInEndpoints: true,
    scopes: {}
}

Mount directories are relative to the app root unless uploads.allowExternalDirectories is enabled for absolute/external paths. CordovaBox creates missing upload directories and ignores invalid mount paths safely; root / mounts require uploads.allowRootMount. Built-in upload endpoints remain enabled by default for POST /api/upload/image, POST /api/upload/video, and GET /api/media/:id; set builtInEndpoints: false or endpoints: false to disable those hook-owned routes.

Apps can add authenticated scoped upload endpoints without changing the generic upload routes. Scope templates interpolate sanitized request values such as :userId; when requireAuth is true, app middleware or an app hook that runs before the upload hook must set req.userId. The upload hook uses its own scoped auth policy to return 401 when that contract is missing; it does not pull global route policies into these hook-owned routes.

uploads: {
    mounts: [{ urlPath: '/uploads', directory: process.env.UPLOAD_ROOT || 'uploads' }],
    scopes: {
        profilePhoto: {
            route: '/api/upload/profile-photo',
            fieldName: 'image',
            kind: 'image',
            directory: ':userId/photos',
            urlPath: '/uploads/:userId/photos',
            maxBytes: 25 * 1024 * 1024,
            allowedExtensions: ['.jpg', '.jpeg', '.png', '.gif', '.webp'],
            requireAuth: true
        },
        messageImage: {
            route: '/api/upload/message-image',
            fieldName: 'image',
            kind: 'image',
            directory: ':userId/messages',
            urlPath: '/uploads/:userId/messages',
            maxBytes: 5 * 1024 * 1024,
            allowedExtensions: ['.jpg', '.jpeg', '.png', '.gif', '.webp'],
            requireAuth: true
        }
    }
}

Scoped uploads return /uploads/... URLs and metadata. They create a Media record when global.Media is available, but apps can also store the returned metadata directly in their own models.

App-level Waterline model defaults also live in config/config.js. Generated apps default to numeric auto-increment ids, but a string or UUID-style app can override models.attributes.id globally:

models: {
    attributes: {
        id: {
            type: 'string',
            required: true
        }
    }
}

Individual model files win over app defaults:

module.exports = {
    primaryKey: 'uuid',
    attributes: {
        id: false,
        uuid: {
            type: 'string',
            required: true
        }
    }
};

The generated app includes its own README.md with app-specific run commands, environment variables, and folder notes.

Repository Layout

| Path | Purpose | | --- | --- | | bin/index.js | CLI entry point for the cordovabox binary. | | bin/build.js | Implements app and API scaffolding commands. | | bin/app/ | Template copied by cordovabox new <appname>. | | lib/app.js | Main runtime app bootstrap used by start and dev. | | lib/hooks/ | Hook modules loaded during bootstrap. | | skills/cordovabox-skill/ | Codex skill with agent-facing project instructions. |

Development

CordovaBox targets Node.js 22 or newer.

Install dependencies:

npm install

Useful repository checks:

node bin/index.js -v
node bin/index.js new MyProject

The root npm test command runs the tests/ folder. Hook-owned tests should live with the hook at lib/hooks/<hookName>/tests/index.js; tests/hook-test-loader.js is discovered by Mocha and loads those hook-local suites. The runtime hook loader ignores tests directories and only registers modules that export register(app, server, cb). Run app build commands from a generated app directory unless a root build file is present.

Generated app commands:

npm run buildDev
npm run build
npm test
npm start

Dependency baseline:

  • Express 5.
  • Socket.IO 4.8 with @socket.io/redis-adapter and redis for clustered sockets.
  • Winston 3 through the CordovaBox logger wrapper, including optional formatted timestamps via _Config.logger and the hook-owned /admin/logs runtime tail.
  • Waterline 0.15 with Sails datastore adapters 2.x and configurable app-level model defaults via _Config.models.
  • Generated apps use Gulp 5 with a lean asset-copy/minification pipeline.

Realtime Routes And Model Events

Generated apps include assets/js/CordovaBoxClient.js, a small browser helper for CRUD requests, auth tokens, CSRF, and route-shaped Socket.IO channels:

<script src="/socket.io/socket.io.js"></script>
<script>
window.CordovaBoxClientConfig = {
    environment: 'production',
    showStartupLogo: true,
    csrfEnabled: true
};
</script>
<script src="/js/CordovaBoxClient.js?v=<%= stats.version %>"></script>
async function loadNewsletter() {
    CordovaBoxClient.setToken(token);

    const records = await CordovaBoxClient.get('/newsletter');
    CordovaBoxClient.renderNewsletterSignups('#newsletterSignups', records);

    await CordovaBoxClient.on('/newsletter', function(envelope) {
        console.log(envelope.channel, envelope.event, envelope.data);
    });

    await CordovaBoxClient.post('/app/newsletter/subscribe', {
        email: '[email protected]'
    });
}

Prefer CordovaBoxClient over raw fetch() or direct io() calls when browser code needs CordovaBox auth tokens, CSRF, route policy handling, socket-backed route execution, retries, request timeouts, or route-shaped realtime subscriptions.

Use CordovaBoxClient.request({ method, url, data, headers, timeout }, callback) when code needs a Sails-style response object. The promise resolves with the response body; the optional callback receives (body, jwres), where jwres includes statusCode, headers, body, error, and transport.

request() uses HTTP by default. Set transport: 'socket' or socket: true to send the request over Socket.IO and execute a configured CordovaBox route through the same controller and HTTP policy stack. Socket-backed requests use cordovabox:request, queue behind the active socket connection attempt, and reject with socket transport metadata on timeout, abort, or unavailable Socket.IO. If /socket.io/socket.io.js is missing, HTTP helpers keep working and realtime calls return socket transport errors. Retries are opt-in with retries or retry: true; only idempotent methods retry by default unless retryMethods or retryUnsafe: true is set.

Lifecycle-aware browser code can call configure, connect, disconnect, reconnect, isConnected, and isConnecting. Use CordovaBoxClient.create(options) for isolated client instances with separate auth tokens, sockets, subscriptions, and request queues. TypeScript-aware editors can load the ambient definition from docs/types/CordovaBoxClient.d.ts; scaffold docs copy the same file under /docs/types/CordovaBoxClient.d.ts.

When csrfEnabled: true is set before the versioned /js/CordovaBoxClient.js script loads, the client asks GET /csrf before the first unsafe request and reuses that token for POST, PUT, PATCH, and DELETE calls through x-csrf-token. The server CSRF hook validates the same unsafe methods when _Config.http.csrf is true. Generated homepages set this flag from _Config.http.csrf. Apps may still pass their own CSRF header per request; explicit csrf-token, xsrf-token, x-csrf-token, or x-xsrf-token headers are preserved and the client does not fetch a replacement token for that request.

Routes that must verify a third-party signature instead of CordovaBox CSRF, such as webhooks, can opt out in config/routes.js:

{
    route: '/webhooks/stripe',
    method: 'post',
    controller: 'WebhookController',
    action: 'stripe',
    csrf: false
}

Hook-owned admin HTML forms inject _csrf hidden inputs when CSRF is enabled. Browser code that uses CordovaBoxClient can rely on the automatic header instead.

Route-shaped socket channels are authorization requests, not raw room names. Clients call CordovaBoxClient.on() with a route-like channel such as /api/messages/between/carmen; the sockets hook applies socket policies and resolves that request to a safe internal room such as conversation:<id>.

Configure route-shaped channels in config/config.js:

socketRoutes: {
    '/newsletter': {
        room: 'resource:newsletter'
    },
    '/api/messages/between/:userSlug': {
        policies: ['isTokenAuthenticated'],
        resolve: async function({ socket, params }) {
            var conversation = await messageService.findConversation(socket.userId, params.userSlug);
            return {
                room: 'conversation:' + conversation.id
            };
        }
    }
}

The sockets hook exposes helpers through app.sockets and global.Sockets:

Sockets.emitRoom('resource:newsletter', 'newsletter.created', subscription);
Sockets.emitUser(user.id, 'notification.created', notification);
Sockets.emitRoute('/api/messages/between/:userSlug', {
    userSlug: 'carmen'
}, 'message.created', message);

Sockets.subscribeResource(req.socket, 'Newsletter');
Sockets.subscribeResource(req.socket, 'Newsletter', subscription.id);
Sockets.publishCreate('Newsletter', subscription);
Sockets.publishUpdate('Newsletter', subscription.id, subscription);

Sockets.join, Sockets.leave, and Sockets.broadcast are available for code migrating from sails.sockets room helpers. Prefer subscribeResource, unsubscribeResource, publishCreate, publishUpdate, and publishDestroy for model/resource PubSub because they normalize room names to safe internal values such as resource:newsletter and resource:newsletter:7.

Realtime envelopes have this shape:

{
    channel: '/api/messages/between/carmen',
    event: 'message.created',
    data: message,
    timestamp: '2026-05-02T00:00:00.000Z'
}

await CordovaBoxClient.on(route, handler, options) registers the handler and resolves with the server channel ack. Rejected channel requests throw an Error whose result property contains the server response. CordovaBoxClient.off(route, handler) leaves the route-shaped channel.

The browser client shows the large CordovaBoxClient startup logo by default. Set window.CordovaBoxClientConfig.showStartupLogo = true before loading /js/CordovaBoxClient.js when the logo should remain visible in every environment, including production. Explicit hide flags still switch to compact socket status lines such as --={||}=-- CordovaBoxClient socket plugged.

Migration note from the previous route-shaped socket version to this version: subscription envelopes keep a single event string such as 'message.created'. Prefer await CordovaBoxClient.on(route, handler, options) for new code. CordovaBoxClient.subscribe(route, handler, options) remains available as a compatibility alias.

Migration note from the previous CordovaBoxClient CSRF behavior: older clients did not fetch /csrf or attach CSRF headers automatically. If an app enables _Config.http.csrf, set window.CordovaBoxClientConfig.csrfEnabled = true before loading /js/CordovaBoxClient.js, or keep passing explicit CSRF headers in request options. Use the correctly spelled csrfEnabled; the misspelled csfrEnabled option is ignored.

CordovaBoxClient compatibility policy: request(options, callback), the jwres response shape, CRUD helpers, route-channel on/off, subscribe/unsubscribe, setToken, and logout remain stable public methods through the current major version. Prefer on(route, handler, options) over subscribe() in new code and prefer request({ transport: 'socket' }) when Socket.IO should execute configured server routes instead of raw custom socket events.

Route transport metadata keeps HTTP and socket execution explicit. Routes default to both transports; use transports: ['http'] for HTTP-only routes and transports: ['socket'] for Socket.IO-only route execution. httpOnly: true, socketOnly: true, socket: false, and http: false remain compatibility shorthands, but new routes should prefer transports.

The modelEvents hook wraps loaded Waterline model globals after the models hook runs. When a configured operation resolves, CordovaBox still emits backward-compatible Socket.IO events named by model route, by model identity, and by model:event. If a matching socketRoutes entry exists, model events are also emitted to the resolved internal route room. A Newsletter.create(...).fetch() emits /newsletter, newsletter, and model:event with a bounded payload containing model, operation, verb, data, and timestamp, and it also publishes resourceful cordovabox:event envelopes through Sockets.publishModelEvent().

Generated apps keep this mutation-only by default so production-sized find() results are not broadcast during normal reads:

modelEvents: {
    enabled: true,
    operations: ['create', 'createEach', 'update', 'updateOne', 'destroy', 'destroyOne'],
    emitReads: false,
    includeReadPayloads: false,
    maxPayloadRecords: 50,
    maxPayloadBytes: 65536
}

Migration note from CordovaBox 2.1.0: find and findOne are opt-in read events. Add emitReads: true and list those operations only for apps that intentionally need read notifications. Read events publish { model, operation, count, truncated } summaries by default; includeReadPayloads: true includes records only under maxPayloadRecords and maxPayloadBytes.

App And Hook Boundaries

lib/app.js owns Express application setup: app creation, server startup, and hook bootstrap. Static serving belongs to the static hook, session/cookie middleware belongs to the session hook, and Redis client setup belongs to the redis hook.

Hooks under lib/hooks/<hookName>/index.js receive app and server from the hook loader and register behavior through that app. Hooks should stay decoupled: do not require sibling hook internals from another hook. When hooks need shared framework behavior, wire it through lib/app.js, _Config, globals that are part of CordovaBox's public runtime contract, or explicit interfaces on app.

Allowed hook imports are same-hook helper files, package/core modules, and neutral shared libraries outside lib/hooks, such as lib/http, lib/routing, lib/security, lib/realtime, or lib/logging. Forbidden imports are any direct reach into another hook folder, such as ../routes, ../../hooks/routes, or another hook's local helper. Runtime services owned by a hook must flow through app, such as app.criteria from the criteria hook and app.policies from the policies hook; downstream hooks should throw a clear error if that app interface is missing. Shared pure behavior can live in a neutral lib/<domain> module, but hook-owned services should still be exposed by the owning hook. tests/hook-independence.js enforces this boundary and should be run after hook changes.

The static hook serves _Config.site.rootFolder or public with index: false. It runs after controllers load and before configured app routes, so static assets such as /js/CordovaBoxClient.js win over catch-all routes while / can still render EJS through ApplicationController.

The rawBody hook owns route-level raw/text body behavior. The bodyParser hook owns normal JSON/form parsing and is ordered after rawBody, so it can skip raw/text routes before consuming the request stream. lib/app.js must not mount global express.json() or express.urlencoded() before hooks load.

Preserve existing public function signatures when changing core framework code. Add optional config fields or helpers where needed, but do not restructure hook methods, controller action calls, class constructors, or function inputs/outputs without deciding that change deliberately first.

Migrating Express Apps

Migrate existing Express apps into CordovaBox by moving behavior into CordovaBox MVC pieces instead of mounting the old Express app.

  • Move old route handlers into api/controllers/*Controller.js.
  • Move reusable business logic into api/services/*.js.
  • Move route middleware into api/policies/httpRequests/*.js and reference it from config/routes.js.
  • Move app-level reusable middleware into a dedicated hook only when it is framework/app infrastructure.
  • Configure datastores, upload mounts, raw-body routes, logger/serverLogger behavior, and model defaults through config/config.js and config/routes.js.
  • Move EJS views into views/ and assets into assets/, then let the generated build copy them into public/.

Do not mount the previous Express app with app.use(oldApp) inside CordovaBox. That bypasses route policies, hook order, generated CRUD conventions, and framework-owned middleware.

Runtime Configuration

Common environment variables:

| Variable | Purpose | | --- | --- | | PORT | HTTP port, defaulting to 3000. | | MONGO_URL | MongoDB connection string when using Mongo-backed models. | | REDIS_URL | Redis connection string for sessions, cache, or Socket.io clustering when enabled. | | SESSION_SECRET | Express session secret when sessions are enabled. |

Example:

$env:PORT=4000
cordovabox start

Hooks

Runtime behavior is composed from hook modules under lib/hooks/<hookName>/index.js. lib/hooks/index.js discovers hook files, sorts them, then requires and registers them in a deliberate production order:

config, redis, session, rawBody, bodyParser, csrf, http, ipaddress, cors,
policies, criteria, models, modelEvents, services, controllers, static,
routes, sockets, ejs, upload, adminAuth, apiDocs, admin, serverLogger, stats, cache

Generated apps can partially override hook order in config/config.js:

hooks: {
  order: []
}

Hooks listed in hooks.order load first in that order. Omitted framework hooks keep their default relative order afterward.

Agent Support

Agent-specific guidance lives in skills/cordovabox-skill/SKILL.md. That file is the best place for Codex-facing workflow notes, source-of-truth reminders, and caveats about this repository. Human-facing usage belongs in this README and in bin/app/README.md.