Skip to content

main.js — Backend Routes

main.js is the backend entry point for your EA plugin. It runs on the server side and lets you define Express routes, access the plugin database, protect endpoints, and optionally bootstrap a full NestJS application.

To enable it, add "main": "main.js" to your setting.json:

json
{
    "versionNumber": "0",
    "version": "1.0.0",
    "name": "my-plugin",
    "init": true,
    "type": "EA",
    "main": "main.js",
    "script": "script.js",
    "id": "unique-plugin-id"
}

Function Signature

main.js must export a function that receives a context object:

js
module.exports = ({ server, Router, app, verifyToken, databasePath, users, io }) => {
    // your backend logic
}

Context Parameters

ParameterTypeDescription
serverExpressThe Express server instance. Mount your routes on it
RouterfunctionExpress Router constructor for creating route groups
appobjectPlugin utilities — database access, helpers (see below)
verifyTokenmiddlewareExpress middleware that validates the user's auth token. Use on protected routes
databasePathstringAbsolute path to the plugin's database/ folder
usersobjectAccess to the platform's users service
ioSocket.IOSocket.IO server instance for real-time events

app Utilities

app.database(name)

Returns a database accessor for the given database namespace (folder name inside database/).

js
const db = app.database("my-plugin");

.readJson(path[])

Reads and parses a JSON file. Path is an array of path segments relative to the database namespace folder.

js
const data = app.database("my-plugin").readJson(["data.json"]);

.writeJson(path[], data)

Serializes and writes data to a JSON file.

js
app.database("my-plugin").writeJson(["data.json"], data);

app.getRndString()

Generates a random unique string, useful for creating record IDs.

js
const id = app.getRndString();
// → "_a3f92bc1d4"

Pattern 1 — Simple Express Router

Use this pattern for lightweight REST endpoints. All routes are mounted under /api/<your-prefix>.

js
module.exports = ({ server, Router, app, verifyToken }) => {
    let API = Router();

    // Public route
    API.post("/contact", (req, res) => {
        let data = app.database("my-plugin").readJson(["contact.json"]);
        data.unshift(req.body);
        app.database("my-plugin").writeJson(["contact.json"], data);
        res.json(true);
    });

    // Protected routes (require auth token)
    API.get("/items/:id", (req, res) => {
            let data = app.database("my-plugin").readJson(["items.json"]);
            res.json(data[req.params.id]);
        })
        .post("/items", verifyToken, (req, res) => {
            let data = app.database("my-plugin").readJson(["items.json"]);
            let body = req.body;
            body.id = app.getRndString();
            data[body.id] = body;
            app.database("my-plugin").writeJson(["items.json"], data);
            res.json(true);
        })
        .put("/items/:id", verifyToken, (req, res) => {
            let data = app.database("my-plugin").readJson(["items.json"]);
            data[req.params.id] = req.body;
            app.database("my-plugin").writeJson(["items.json"], data);
            res.json(true);
        })
        .delete("/items/:id", verifyToken, (req, res) => {
            let data = app.database("my-plugin").readJson(["items.json"]);
            delete data[req.params.id];
            app.database("my-plugin").writeJson(["items.json"], data);
            res.json(true);
        });

    server.use("/api/my-plugin", API);
}

Routes are available at /api/my-plugin/items, /api/my-plugin/contact, etc.

Pattern 2 — NestJS Bootstrap

For complex plugins, you can bootstrap a full NestJS application from main.js. The NestJS app is built separately and its compiled output is placed in database/backend/.

js
const { join } = require("path");

module.exports = async ({ server, databasePath, app, users, io }) => {
    const backendPath = join(databasePath, "backend", "dist", "src");
    const { bootstrap } = require(join(backendPath, "main"));

    // Set environment variables your NestJS app needs
    process.env['DATABASE_PATH'] = databasePath;
    process.env['APP_SECRET'] = "your-secret";

    return await bootstrap({
        server,
        app,
        users,
        io,
        databasePath
    });
};

NestJS File Structure

When using the NestJS pattern, your plugin's database/ folder holds the compiled backend output:

my-plugin/
├── 1.0.0/
│   ├── setting.json
│   ├── script.js
│   └── main.js           ← bootstraps the NestJS app
└── database/
    └── my-plugin/
        └── backend/
            └── dist/
                └── src/
                    └── main.js    ← compiled NestJS entry point

The NestJS project lives outside the plugin folder and is developed separately. Only the compiled dist/ output is placed in database/backend/ when deploying.

Using verifyToken

Add verifyToken as middleware on any route that requires the user to be authenticated:

js
// Public — no auth required
API.get("/public-data", (req, res) => { ... });

// Protected — must send a valid token
API.post("/private-data", verifyToken, (req, res) => { ... });
API.put("/private-data/:id", verifyToken, (req, res) => { ... });
API.delete("/private-data/:id", verifyToken, (req, res) => { ... });

Requests to protected routes without a valid token receive a 401 Unauthorized response automatically.

Database JSON Structure

Design your database/ JSON files to match what your routes read and write. Example for a simple items store:

json
{
    "_a3f92bc1d4": {
        "id": "_a3f92bc1d4",
        "name": "Item One",
        "price": 25
    },
    "_b7e01cd2f5": {
        "id": "_b7e01cd2f5",
        "name": "Item Two",
        "price": 40
    }
}

Using an object keyed by ID (from app.getRndString()) makes lookups, updates, and deletes by ID fast and straightforward.