Configuration
Every option of new Forge, what it does, its default and how invalid settings are reported
You configure StarkForge with one options object: new Forge(options). Every option is optional, so new Forge() starts a development server, and you add options as your project needs them. This page is the reference for all of them. StarkForge reads no environment variables itself; your code reads them and passes the values in, as Environment Variables shows.
A Realistic Example
This server sets the options most projects change first. It reads secrets and addresses from the environment and keeps the rest in code:
// src/server.ts
import { Forge } from "@starklabs/forge";
const forge = new Forge({
ENV: process.env.NODE_ENV,
port: Number(process.env.PORT ?? 5000),
backendURL: process.env.BACKEND_URL,
frontendURL: process.env.FRONTEND_URL,
mongoDBURI: process.env.MONGODB_URI,
databaseName: "acme",
jwtSecret: process.env.JWT_SECRET,
masterKey: process.env.MASTER_KEY,
trustProxy: 1,
bodyLimit: "1mb",
rateLimitDuration: "10m",
maxReqLimit: 300,
authMaxReqLimit: 10,
systemEmailSender: "Acme <hello@acme.example>",
resendAPIKey: process.env.RESEND_API_KEY,
defaultPageSize: 25,
maxPageSize: 100,
});
await forge.start();An option whose value is undefined, such as an unset environment variable, behaves as if you had not written it.
Server Options
| Option | Type | Default | What it does |
|---|---|---|---|
port | number | 5000 | The port to listen on, from 0 to 65535. 0 picks a free port, which tests use |
apiVersion | number | 1 | A positive integer. Routes are mounted under /api/v1, so 2 gives /api/v2 |
ENV | string | "development" | The environment name. "production" changes several defaults (see Production Defaults), and "test" silences logging |
backendURL | string | http://localhost:5000/api/v1, built from port | The public base URL of the API, including the version prefix. Social sign-in uses it to build callback URLs |
frontendURL and frontendURLs | string and string[] | none | The browser origins that may call the API with cookies, for example http://localhost:3000. They are also the only places a redirect may point |
corsConfig | cors options | built from the front-end URLs | Replaces the generated CORS options. Without it, the front-end origins are allowed with credentials, and with no front-end URL at all, cross-origin browser requests are refused |
trustProxy | boolean, number or string | false | The Express trust proxy setting. Set it behind a load balancer so that rate limits see the visitor's address |
logRequests | boolean | true, and false when ENV is "test" | Prints the start-up banner and one log line per request |
exposeErrorDetails | boolean | true outside production | Adds hint and details to error bodies and shows real messages for unexpected errors |
bodyLimit | string | "100kb" | The largest JSON or form body, such as "1mb". A larger body gets 413 PAYLOAD_TOO_LARGE |
Database Options
| Option | Type | Default | What it does |
|---|---|---|---|
isOffline | boolean | true unless ENV is "production" | Development mode for services. The database falls back to mongodb://127.0.0.1:27017, and one-time codes and magic links are printed to the console instead of being emailed. It is rejected in production |
mongoDBURI | string | none | The MongoDB connection string. Required when isOffline is false |
databaseName | string | "starkForge" | The database to use on that server |
Security Options
| Option | Type | Default | What it does |
|---|---|---|---|
jwtSecret | string | none | The key that signs session tokens. At least 32 characters, and required when any sign-in feature is configured. Generate one with generateJWTSecret() |
masterKey | string | none | The key that encrypts fields and two-factor secrets: base64 text of exactly 32 bytes. Generate one with generateMasterKey(). Losing it makes encrypted data unreadable |
adminRoles | string[] | ["admin"] | The roles that count as administrators. The list must not be empty |
protectedFields | string[] | ["role"] | Fields that only administrators may write. Other users' requests have them removed |
Rate Limit Options
Both limits count requests per visitor address inside one window and answer 429 when the count is exceeded.
| Option | Type | Default | What it does |
|---|---|---|---|
rateLimitDuration | duration | "15m" | The length of the window for both limits |
maxReqLimit | number | 100 | Requests allowed per window for every route. Must be positive |
authMaxReqLimit | number | 20 | A stricter shared allowance for sign-up, sign-in, one-time code and password reset routes. Must be positive |
rateLimitMsg | string | "Too many requests, please try again later." | The message of the 429 answer |
rateLimitStore | store | in memory | A store from express-rate-limit. Use a shared one, such as Redis, when several instances run |
Email Options
| Option | Type | Default | What it does |
|---|---|---|---|
systemEmailSender | string | none | The sender of system emails such as codes and links. It must look like Acme <hello@acme.example> |
resendAPIKey | string | none | The Resend API key. Real email is sent only when this is set and isOffline is false |
Upload Options
| Option | Type | Default | What it does |
|---|---|---|---|
cloudinaryAPIKey, cloudinaryAPISecret and cloudinaryCloudName | string | none | Your Cloudinary credentials. All three are required as soon as one route accepts files |
cloudinaryFolderName | string | "starkForge" | The Cloudinary folder that receives the files |
uploadMaxFileSize | number | 5242880 | The largest single file in bytes, which is 5 MiB |
uploadMaxFiles | number | 10 | The most files in one request |
Pagination Options
| Option | Type | Default | What it does |
|---|---|---|---|
defaultPageSize | number | 50 | The page size of a list when the request has no limit. It must not be larger than maxPageSize |
maxPageSize | number | 100 | The largest page size of a list. A larger limit in a request is lowered to this value |
Collections and Built-in Features
| Option | Type | Default | What it does |
|---|---|---|---|
collections | collection[] | [] | Your resources. See Collections |
builtinConfig | object | none | Settings for features that StarkForge generates for you: auth, account and email. The Authentication page covers the first two. The email key holds the placeholder values of system emails: companyName, companyUrl, companyAddress, supportEmail and unsubscribeUrl |
Production Defaults
Setting ENV to "production" turns on stricter defaults. Each row below is a default that you can still override, except the offline rule, which is enforced.
| Setting | Development | Production |
|---|---|---|
isOffline | true | false, and true is rejected |
mongoDBURI | Optional | Required |
exposeErrorDetails | true | false, so errors carry no hint or details and unexpected errors answer Something went wrong. |
| Log detail | Debug | Info |
Auth cookie sameSite | "lax" | "none", for a front end on another site |
Auth cookie secure | false | true, so cookies travel only over HTTPS |
| One-time codes and magic links | Printed to the console | Emailed, which needs resendAPIKey and systemEmailSender |
The Deployment page lists everything else to check before you go live.
Duration Values
Options such as rateLimitDuration take a duration: a number followed by a unit, with no space. The number may have decimals, as in "1.5h".
| Unit | Meaning | Example |
|---|---|---|
ms | Milliseconds | "500ms" |
s | Seconds | "30s" |
m | Minutes | "15m" |
h | Hours | "2h" |
d | Days | "7d" |
w | Weeks | "2w" |
mo | Months of 30 days | "1mo" |
y | Years of 365 days | "1y" |
Invalid Settings
new Forge checks every option immediately and throws one ConfigurationError that lists every problem it found, so you can fix them in one pass. Its code is CONFIGURATION_ERROR. This is what a missing secret and a missing user model look like:
ConfigurationError (CONFIGURATION_ERROR): Invalid Forge configuration:
- auth.mongooseConfig is required when auth.mode is 'builtin'.
- jwtSecret is required for authentication. Generate one with generateJWTSecret().
hint: Fix the listed options in new Forge({...}).A duration written in words is rejected the same way:
ConfigurationError (CONFIGURATION_ERROR): Invalid Forge configuration:
- rateLimitDuration "15 minutes" is not a valid duration (examples: "30s", "15m", "1d").
hint: Fix the listed options in new Forge({...}).Mistakes inside a collection, such as an unknown handler name, are reported when the app is built, which start() does for you. Their message lists each problem with the method and the path of the route, and the Routes page shows it.
StarkForge does not reject an option name it does not know. In TypeScript the compiler flags a misspelled name for you. In plain JavaScript nothing does, so check the spelling against the tables above.
Warnings
Some problems are not fatal. StarkForge logs a warning when it starts, and forge.config.warnings holds the same list.
| The warning says | What it means |
|---|---|
have routes that require sign-in, but no authentication is configured | Those routes answer 401 to every request. Configure builtinConfig.auth or mark the routes auth: "public" |
builtinConfig.email was not provided | Real email can go out, and its templates may show empty placeholders. Add builtinConfig.email |
corsConfig: origin '*' together with credentials is unsafe | List your front-end origins instead of * |
auth.cookie.secure is false in production | Session cookies would travel over plain HTTP |
domain is deprecated | You used the 1.x top-level domain option. Set builtinConfig.auth.cookie.domain instead |
Reading the Resolved Config
forge.config holds the validated settings with every default filled in. It is read-only, so you can pass it around safely. For example, forge.config.port is the port that was resolved, forge.config.apiPrefix is /api/v1 and forge.config.isProduction tells you which mode you are in.
Where to Go Next
Authentication
Configure sign-up, sign-in and sessions with builtinConfig.
Deployment
Check everything that changes in production.
:::