Configuration
Efesto is configured through the object you pass to efesto(). Every option, with its type and default, is listed below.
The two stacks take different configuration objects. Most of this page documents
the Express object (efesto from "efesto"). The smaller Bun/Elysia object
(efesto/elysia) is documented in its own section
at the end.
Build your configuration
Toggle the options to assemble a starting efesto({ ... }) call for either stack, then
copy it. Each field maps to a real option documented below.
import efesto from "efesto";
app.use(
"/api/v1",
efesto({
authMiddleware: (req, res, next) => next(),
errorMiddleware: (req, res, next) => next(),
isProduction: false,
options: {
absoluteDirRoutes: `${__dirname}/routes`,
generatedTypesFolder: `${__dirname}/efesto-types`,
config: {
defaultRequiredValueForModels: true,
},
},
})
);Efesto Configuration Object (Express)
The Express configuration is passed to the efesto() function and contains the following structure:
efesto({
authMiddleware: Middleware, // Required
errorMiddleware: Middleware, // Required
isProduction: boolean, // Required
options?: { // Optional
absoluteDirRoutes?: string | string[];
generatedTypesFolder?: string;
relativeDirSwaggerDeclarationsPath?: string;
customFormats?: SwaggerFormats;
automaticTypesGenerationInlineFile?: boolean;
config?: ConfigObject;
}
})
Required Parameters
authMiddleware
Type: Middleware
Required: ✅
Description: The authentication middleware that will be applied to all endpoints.
authMiddleware: (req, res, next) => {
// Your authentication logic here
// Example: JWT verification, session validation, etc.
// If authentication fails:
// return res.status(401).json({ error: 'Unauthorized' });
// If authentication succeeds:
next();
};
errorMiddleware
Type: Middleware
Required: ✅
Description: The error handling middleware for processing errors.
errorMiddleware: (req, res, next) => {
// Your error handling logic here
// Example: Logging, error formatting, etc.
next();
};
isProduction
Type: boolean
Required: ✅
Description: Enables production mode. When true, Efesto scans compiled .js route files; when false it scans .ts source files (and enables development-only features such as type generation and magic code editing).
isProduction: process.env.NODE_ENV === "production";
Optional Parameters
options.absoluteDirRoutes
Type: string | string[]
Default: "/v1/routes"
Description: Path(s) where Efesto will find the route files to parse.
// Single path
absoluteDirRoutes: path.join(__dirname, "routes");
// Multiple paths
absoluteDirRoutes: [
path.join(__dirname, "routes"),
path.join(__dirname, "api"),
];
options.generatedTypesFolder
Type: string
Default: undefined
Description: Folder where Efesto will generate TypeScript type definitions.
generatedTypesFolder: path.join(__dirname, "types");
options.relativeDirSwaggerDeclarationsPath
Type: string
Default: "swagger-declarations"
Description: Folder where Efesto will create Swagger YAML files.
relativeDirSwaggerDeclarationsPath: "docs/swagger";
options.customFormats
Type: SwaggerFormats
Default: undefined
Description: Custom Swagger formats for validation.
customFormats: {
'custom-format': {
example: 'custom-value'
}
}
options.automaticTypesGenerationInlineFile
Type: boolean
Default: false
Description: Whether to generate types inline in the same file.
Config Object
The config object contains advanced configuration options:
config: {
createdAtField?: string;
updatedAtField?: string;
dynamicParameterType?: "string" | "number";
defaultRequiredValueForModels?: boolean;
defaultNullableOnOptionalFields?: boolean;
customTypes?: { [key: string]: SwaggerDataTypes | SwaggerSchemaItems };
optionalFieldsType?: "null" | "undefined";
canMagicallyEditCode?: boolean;
verbose?: boolean;
mongoIdParser?: boolean;
abacPermissions?: AbacConfigurationObject;
redis?: RedisConfigurationObject;
}
| Option | Type | Default | Description |
|---|---|---|---|
createdAtField | string | "createdAt" | Field used as the createdAt timestamp |
updatedAtField | string | "updatedAt" | Field used as the updatedAt timestamp |
dynamicParameterType | "string" | "number" | "string" | Documented type of [param] path parameters |
defaultRequiredValueForModels | boolean | true | Fields are required unless marked ? |
defaultNullableOnOptionalFields | boolean | false | Optional (?) fields also become nullable |
optionalFieldsType | "null" | "undefined" | "undefined" | Type of optional fields (use "null" with Prisma) |
customTypes | Record<string, …> | undefined | Reusable named types for Magic Types (see below) |
canMagicallyEditCode | boolean | true | Let Efesto rewrite method signatures (dev only) |
verbose | boolean | false | Verbose startup logging |
mongoIdParser | boolean | false | Map _id to id in responses |
abacPermissions | object | undefined | ABAC config |
redis | object | undefined | Redis config |
customTypes maps a name to a base type or schema, then you use the name like any
Magic Type:
customTypes: {
ObjectId: "string",
Date: { type: "string", format: "date-time" },
Decimal: { type: "number", format: "decimal" },
}
The type-generation options (generatedTypesFolder,
automaticTypesGenerationInlineFile, canMagicallyEditCode) are covered in
Type Generation.
ABAC Configuration
abacPermissions
Type: AbacConfigurationObject
Default: undefined
Description: Configuration for Attribute-Based Access Control.
abacPermissions: {
actions?: string[];
models?: string[];
checkPermissionBeforeResolver?: boolean;
reqAbilityField?: string;
}
ABAC Configuration Options
actions: Array of allowed actions (e.g.,["read", "write", "delete"])models: Array of model names to apply permissions tocheckPermissionBeforeResolver: Whether to check permissions before executing resolversreqAbilityField: Field name in request object containing the ability (default:"ability")
abacPermissions: {
actions: ["read", "write", "delete"],
models: ["User", "Post", "Comment"],
checkPermissionBeforeResolver: true,
reqAbilityField: "userAbility"
}
Redis Configuration
redis
Type: RedisConfigurationObject
Default: undefined
Description: Configuration for Redis caching.
redis: {
host: string;
port: number;
defaultExpiresInSeconds: number;
}
Redis Configuration Options
host: Redis server hostnameport: Redis server portdefaultExpiresInSeconds: Default cache expiration time
redis: {
host: "127.0.0.1",
port: 6379,
defaultExpiresInSeconds: 600
}
Environment Variables
You can use environment variables to configure Efesto:
// .env file
NODE_ENV=development
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_EXPIRES=600
EFESTO_VERBOSE=true
EFESTO_TYPES_FOLDER=./types
// In your configuration
efesto({
authMiddleware: authMiddleware,
errorMiddleware: errorMiddleware,
isProduction: process.env.NODE_ENV === "production",
options: {
absoluteDirRoutes: "./routes",
generatedTypesFolder: process.env.EFESTO_TYPES_FOLDER,
config: {
verbose: process.env.EFESTO_VERBOSE === "true",
redis: process.env.REDIS_HOST
? {
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT || "6379"),
defaultExpiresInSeconds: parseInt(
process.env.REDIS_EXPIRES || "600"
),
}
: undefined,
},
},
});
Complete Configuration Example
import express from "express";
import efesto from "efesto";
import path from "path";
const app = express();
// Middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Authentication middleware
const authMiddleware = (req, res, next) => {
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token) {
return res.status(401).json({ error: "No token provided" });
}
try {
// Verify JWT token
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ error: "Invalid token" });
}
};
// Error middleware
const errorMiddleware = (req, res, next) => {
console.error("Error:", req.error);
res.status(500).json({
error: "Internal Server Error",
message:
process.env.NODE_ENV === "development" ? req.error?.message : undefined,
});
};
// Efesto configuration
app.use(
"/api/v1",
efesto({
authMiddleware,
errorMiddleware,
isProduction: process.env.NODE_ENV === "production",
options: {
absoluteDirRoutes: path.join(__dirname, "routes"),
generatedTypesFolder: path.join(__dirname, "types"),
relativeDirSwaggerDeclarationsPath: "swagger-declarations",
automaticTypesGenerationInlineFile: false,
config: {
createdAtField: "createdAt",
updatedAtField: "updatedAt",
dynamicParameterType: "string",
defaultRequiredValueForModels: true,
defaultNullableOnOptionalFields: false,
optionalFieldsType: "undefined",
canMagicallyEditCode: true,
verbose: process.env.NODE_ENV === "development",
mongoIdParser: false,
customTypes: {
ObjectId: "string",
Date: { type: "string", format: "date-time" },
Email: { type: "string", format: "email" },
},
abacPermissions: {
actions: ["read", "write", "delete"],
models: ["User", "Post", "Comment"],
checkPermissionBeforeResolver: true,
reqAbilityField: "ability",
},
redis: process.env.REDIS_HOST
? {
host: process.env.REDIS_HOST,
port: parseInt(process.env.REDIS_PORT || "6379"),
defaultExpiresInSeconds: parseInt(
process.env.REDIS_EXPIRES || "600"
),
}
: undefined,
},
},
})
);
export default app;
Bun/Elysia configuration (efesto/elysia)
The Elysia entrypoint takes a smaller object. It has no authMiddleware,
errorMiddleware, swaggerModel, or YAML generation: validation, OpenAPI, and hooks
are all native Elysia.
import efesto from "efesto/elysia";
const app = efesto({
isProduction: process.env.NODE_ENV === "production", // required
routesDir: `${import.meta.dir}/routes`, // required
prefix: "/api/v1", // optional
swagger: true, // optional (default true)
setup: (root) => { // optional
// add global plugins/hooks before routes mount (auth, ABAC ability, cors, ...)
},
abac: { // optional
abilityField: "ability",
checkPermissionBeforeResolver: true,
},
}).listen(2014);
| Parameter | Type | Required | Description | Default |
|---|---|---|---|---|
isProduction | boolean | ✅ | Scan compiled .js route files instead of .ts | — |
routesDir | string | string[] | ✅ | Directory (or directories) scanned for route modules | — |
prefix | string | ❌ | Mount prefix for the whole app, e.g. /api/v1 | none |
swagger | boolean | OpenApiOptions | ❌ | Enable the native OpenAPI plugin; pass an object to customize it | true |
setup | (app) => unknown | ❌ | Add global plugins/hooks on the root before routes are mounted | — |
abac | { abilityField?, checkPermissionBeforeResolver? } | ❌ | Tune class-based permission enforcement | see ABAC |
efesto(...) returns a native Elysia instance, so you can .listen(...) it or mount
it as a plugin with new Elysia().use(efesto(...)).