Advanced
Effect-TS API
Integrate nestjs-openapi with Effect-TS
nestjs-openapi uses Effect-TS internally. If you already use Effect in your project, you can use the library's Effect-based API directly for typed error handling.
Why Effect?
Effect provides:
- Typed errors - Know exactly what errors can occur
- Composable operations - Chain operations with automatic error propagation
- Resource safety - Automatic cleanup
- Structured concurrency - Safe parallel operations
- Observability - Built-in logging and tracing
Effect-Based Generation
Basic Usage
import { Effect } from 'effect';
import { generateEffect } from 'nestjs-openapi';
const program = generateEffect({
tsconfig: './tsconfig.json',
entry: './src/app.module.ts',
});
// Run the effect
const paths = await Effect.runPromise(program);
console.log('Generated paths:', Object.keys(paths));With Error Handling
import { Effect, pipe } from 'effect';
import { generateEffect } from 'nestjs-openapi';
import type { ProjectError } from 'nestjs-openapi';
const program = pipe(
generateEffect({
tsconfig: './tsconfig.json',
entry: './src/app.module.ts',
}),
Effect.catchTag('EntryNotFoundError', (error) =>
Effect.succeed({
error: `Entry not found: ${error.entry}`,
paths: {},
}),
),
Effect.catchTag('ProjectInitError', (error) =>
Effect.succeed({
error: `Project init failed: ${error.message}`,
paths: {},
}),
),
);
const result = await Effect.runPromise(program);GenerateOptions
interface GenerateOptions {
/** Path to tsconfig.json */
readonly tsconfig: string;
/** Path to the entry module file */
readonly entry: string;
}Async Wrapper
For convenience, there's also an async wrapper:
import { generateAsync } from 'nestjs-openapi';
// Returns Promise<OpenApiPaths>
const paths = await generateAsync({
tsconfig: './tsconfig.json',
entry: './src/app.module.ts',
});Error Types
The Effect API uses typed errors:
import type { ProjectError } from 'nestjs-openapi';
import { EntryNotFoundError, ProjectInitError } from 'nestjs-openapi';
// ProjectError is a union type:
type ProjectError = EntryNotFoundError | ProjectInitError;EntryNotFoundError
class EntryNotFoundError {
readonly _tag = 'EntryNotFoundError';
readonly entry: string;
readonly className: string;
readonly message: string;
}ProjectInitError
class ProjectInitError {
readonly _tag = 'ProjectInitError';
readonly tsconfig: string;
readonly message: string;
readonly cause?: unknown;
}Using with Effect Runtime
Custom Runtime
import { Effect, Runtime, Layer, Logger, LogLevel } from 'effect';
import { generateEffect } from 'nestjs-openapi';
// Create a custom runtime with logging
const CustomRuntime = Runtime.make({
logger: Logger.pretty,
logLevel: LogLevel.Debug,
});
const program = generateEffect({
tsconfig: './tsconfig.json',
entry: './src/app.module.ts',
});
const paths = await Runtime.runPromise(CustomRuntime)(program);With Logging
The Effect API logs progress using Effect's logging system:
import { Effect, Logger, LogLevel } from 'effect';
import { generateEffect } from 'nestjs-openapi';
const program = generateEffect({
tsconfig: './tsconfig.json',
entry: './src/app.module.ts',
}).pipe(Logger.withMinimumLogLevel(LogLevel.Info));
// Logs:
// [INFO] Starting OpenAPI generation { entry: './src/app.module.ts' }
// [INFO] Collected method infos { modules: 5, methods: 42 }
// [INFO] OpenAPI generation complete { paths: 15 }Composing with Other Effects
Sequential Operations
import { Effect, pipe } from 'effect';
import { generateEffect } from 'nestjs-openapi';
import * as fs from 'fs/promises';
const program = pipe(
generateEffect({
tsconfig: './tsconfig.json',
entry: './src/app.module.ts',
}),
Effect.flatMap((paths) =>
Effect.tryPromise(() =>
fs.writeFile('openapi.json', JSON.stringify({ paths }, null, 2)),
),
),
Effect.tap(() => Effect.logInfo('OpenAPI spec written')),
);
await Effect.runPromise(program);Parallel Generation
import { Effect } from 'effect';
import { generateEffect } from 'nestjs-openapi';
const configs = [
{
tsconfig: './apps/api/tsconfig.json',
entry: './apps/api/src/app.module.ts',
},
{
tsconfig: './apps/admin/tsconfig.json',
entry: './apps/admin/src/app.module.ts',
},
];
const program = Effect.all(
configs.map((config) =>
generateEffect(config).pipe(Effect.map((paths) => ({ config, paths }))),
),
{ concurrency: 'unbounded' },
);
const results = await Effect.runPromise(program);With Error Recovery
import { Effect, pipe } from 'effect';
import { generateEffect } from 'nestjs-openapi';
const program = pipe(
generateEffect({
tsconfig: './tsconfig.json',
entry: './src/app.module.ts',
}),
Effect.retry({
times: 3,
schedule: Schedule.exponential('100 millis'),
}),
Effect.catchAll((error) =>
Effect.succeed({ error: error.message, paths: {} }),
),
);Lower-Level APIs
For even more control, you can use the lower-level APIs:
import { Effect } from 'effect';
import {
getModules,
getControllerMethodInfos,
transformMethods,
} from 'nestjs-openapi';
import { Project } from 'ts-morph';
const program = Effect.gen(function* () {
// Initialize ts-morph project
const project = new Project({
tsConfigFilePath: './tsconfig.json',
skipAddingFilesFromTsConfig: true,
});
project.addSourceFilesAtPaths('./src/app.module.ts');
const entryFile = project.getSourceFile('./src/app.module.ts');
const appModule = entryFile?.getClass('AppModule');
if (!appModule) {
return yield* Effect.fail(new Error('AppModule not found'));
}
// Get all modules
const modules = yield* getModules(appModule);
// Extract method info from each controller
const methodInfos = modules.flatMap((mod) =>
mod.controllers.flatMap((controller) =>
getControllerMethodInfos(controller),
),
);
// Transform to OpenAPI paths
const paths = transformMethods(methodInfos);
return { modules: modules.length, paths };
});
const result = await Effect.runPromise(program);Type Reference
OpenApiPaths
interface OpenApiPaths {
readonly [path: string]: {
readonly [method: string]: OpenApiOperation;
};
}MethodInfo
interface MethodInfo {
readonly controllerName: string;
readonly controllerPath: string;
readonly methodName: string;
readonly httpMethod: HttpMethod;
readonly path: string;
readonly parameters: readonly ResolvedParameter[];
readonly returnType?: ReturnTypeInfo;
readonly tags: readonly string[];
readonly summary?: string;
readonly description?: string;
readonly operationId?: string;
readonly deprecated?: boolean;
readonly responses: readonly ResponseInfo[];
readonly consumes?: readonly string[];
readonly produces?: readonly string[];
readonly decorators: readonly string[];
}See Also
- Effect Documentation - Learn Effect-TS
- Errors Reference - All error types
- Internals - How the library works