Skip to main content

Configuration

Efesto is configured through the object you pass to efesto(). Every option, with its type and default, is listed below.

Two configuration objects

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;
}
OptionTypeDefaultDescription
createdAtFieldstring"createdAt"Field used as the createdAt timestamp
updatedAtFieldstring"updatedAt"Field used as the updatedAt timestamp
dynamicParameterType"string" | "number""string"Documented type of [param] path parameters
defaultRequiredValueForModelsbooleantrueFields are required unless marked ?
defaultNullableOnOptionalFieldsbooleanfalseOptional (?) fields also become nullable
optionalFieldsType"null" | "undefined""undefined"Type of optional fields (use "null" with Prisma)
customTypesRecord<string, …>undefinedReusable named types for Magic Types (see below)
canMagicallyEditCodebooleantrueLet Efesto rewrite method signatures (dev only)
verbosebooleanfalseVerbose startup logging
mongoIdParserbooleanfalseMap _id to id in responses
abacPermissionsobjectundefinedABAC config
redisobjectundefinedRedis 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 to
  • checkPermissionBeforeResolver: Whether to check permissions before executing resolvers
  • reqAbilityField: 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 hostname
  • port: Redis server port
  • defaultExpiresInSeconds: 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);
ParameterTypeRequiredDescriptionDefault
isProductionbooleanScan compiled .js route files instead of .ts
routesDirstring | string[]Directory (or directories) scanned for route modules
prefixstringMount prefix for the whole app, e.g. /api/v1none
swaggerboolean | OpenApiOptionsEnable the native OpenAPI plugin; pass an object to customize ittrue
setup(app) => unknownAdd global plugins/hooks on the root before routes are mounted
abac{ abilityField?, checkPermissionBeforeResolver? }Tune class-based permission enforcementsee ABAC

efesto(...) returns a native Elysia instance, so you can .listen(...) it or mount it as a plugin with new Elysia().use(efesto(...)).