Upgrade guide
Upgrade from 2.x to 3.x
This guide will walk you through upgrading your ExpressoTS 2.x project to version 3.x.
The latest version introduces several new features, improvements, and breaking changes, so follow the steps carefully to ensure a smooth transition.
Prerequisites
Before starting the upgrade process, ensure you meet the following prerequisites:
- Node.js version 20 or higher.
- An existing project built with ExpressoTS 2.x.
Update the CLI
- Uninstall the old CLI globally:
npm uninstall @expressots/cli -g
- Install the new CLI globally:
npm i @expressots/[email protected] -g
Update project dependencies
- Remove the old ExpressoTS dependencies:
npm uninstall @expressots/adapter-express @expressots/core @expressots/cli
- Install the new ExpressoTS dependencies:
npm i @expressots/[email protected] @expressots/[email protected] @expressots/[email protected]
&& npm i @expressots/[email protected] -D
- Complete list of dependencies to install:
"dependencies": {
        "@expressots/adapter-express": "3.0.0",
        "@expressots/core": "3.0.0",
        "@expressots/shared": "3.0.0"
    },
"devDependencies": {
    "@expressots/cli": "3.0.0",
    "@types/express": "5.0.0",
    "@types/jest": "29.5.14",
    "@types/node": "20.12.7",
    "@typescript-eslint/eslint-plugin": "8.0.0",
    "@typescript-eslint/parser": "8.0.0",
    "eslint": "8.57.0",
    "eslint-config-prettier": "9.0.0",
    "eslint-plugin-prettier": "5.0.0",
    "jest": "29.7.0",
    "prettier": "3.2.5",
    "supertest": "^7.0.0",
    "ts-jest": "29.1.2",
    "tsconfig-paths": "4.2.0",
    "tsx": "4.19.2",
    "typescript": "5.1.3"
}
Update project structure
File expressots.config.ts
The expressots.config.ts file has been updated in version 3.x. Specifically, the imports for ExpressoConfig and Pattern have moved from @expressots/core to @expressots/shared.
Additionally, the entryPoint property was introduced. For non-opinionated projects, remember to set the opinionated option to false to reflect this configuration.
- expressots.config.ts [ 2.x ]
- expressots.config.ts [ 3.x ]
import { ExpressoConfig, Pattern } from "@expressots/core";
const config: ExpressoConfig = {
    sourceRoot: "src",
    scaffoldPattern: Pattern.KEBAB_CASE,
    opinionated: true,
};
export default config;
import { ExpressoConfig, Pattern } from "@expressots/shared";
const config: ExpressoConfig = {
    entryPoint: "main",
    sourceRoot: "src",
    scaffoldPattern: Pattern.KEBAB_CASE,
    opinionated: true,
};
export default config;
File main.ts
The main entry point has been streamlined in version 3.x. You no longer need to pass the container manually, and configuration is now centralized in the app.ts file.
- Opinionated
- Non-Opinionated
- main.ts [ 2.x ]
- main.ts [ 3.x ]
import { AppFactory, ServerEnvironment } from "@expressots/core";
import { App } from "@providers/app/app.provider";
import { container } from "./app.container";
async function bootstrap() {
    const app = await AppFactory.create(container, App);
    await app.listen(3000, ServerEnvironment.Development);
}
bootstrap();
import { AppFactory } from "@expressots/core";
import { App } from "app";
import { env } from "env";
AppFactory.create(App).then((app) =>
    app.listen(env.App.Port, {
        appName: env.App.appName,
        appVersion: env.App.appVersion,
    })
);
- main.ts [ 2.x ]
- main.ts [ 3.x ]
import { AppFactory, ServerEnvironment } from "@expressots/core";
import { container } from "./app.container";
import { App } from "./app.provider";
async function bootstrap() {
    const app = await AppFactory.create(container, App);
    await app.listen(3000, ServerEnvironment.Development);
}
bootstrap();
import { AppFactory } from "@expressots/core";
import { App } from "./app";
AppFactory.create(App).then((app) => app.listen(3000));
File env.ts
Environment management has been simplified and made more dynamic in version 3.x.
- env.ts [ 2.x ]
- env.ts [ 3.x ]
import pkg from "../package.json";
const ENV = {
    Application: {
        APP_NAME: pkg.name,
        APP_VERSION: pkg.version,
        ENVIRONMENT: process.env.ENVIRONMENT as string,
        PORT: Number(process.env.PORT),
    },
};
export default ENV;
import pkg from "../package.json";
export const env = {
    App: {
        appName: pkg.name,
        appVersion: pkg.version,
        get Port() {
            return process.env.PORT || 3000;
        },
    },
};
Replace app.provider.ts with app.ts
The app.provider.ts file has been replaced by a simplified app.ts file in version 3.x. This file now directly integrates modules and middleware setup.
- Opinionated
- Non-Opinionated
- app.provider.ts [ 2.x ]
- app.ts [ 3.x ]
import { AppExpress } from "@expressots/adapter-express";
import { Env, IMiddleware, Middleware, provide, ProviderManager } from "@expressots/core";
import { container } from "../../app.container";
@provide(App)
export class App extends AppExpress {
    private middleware: IMiddleware;
    private provider: ProviderManager;
    constructor() {
        super();
        this.middleware = container.get<IMiddleware>(Middleware);
        this.provider = container.get(ProviderManager);
    }
    protected configureServices(): void {
        this.provider.register(Env);
        this.middleware.addBodyParser();
        this.middleware.setErrorHandler({ showStackTrace: true });
    }
    protected postServerInitialization(): void {
        if (this.isDevelopment()) {
            this.provider.get(Env).checkAll();
        }
    }
    protected serverShutdown(): void {}
}
import { AppExpress } from "@expressots/adapter-express";
import { AppContainer, Env } from "@expressots/core";
import { AppModule } from "@useCases/app/app.module";
export class App extends AppExpress {
    private config: AppContainer = this.configContainer([AppModule]);
    async globalConfiguration(): Promise<void> {
        this.setGlobalRoutePrefix("/v1");
        this.initEnvironment("development", {
            env: {
                development: ".env.development",
                production: ".env.production",
            },
        });
    }
    async configureServices(): Promise<void> {
        this.Provider.register(Env);
        this.Middleware.addBodyParser();
        this.Middleware.setErrorHandler({ showStackTrace: true });
    }
    async postServerInitialization(): Promise<void> {
        if (await this.isDevelopment()) {
            this.Provider.get(Env).checkFile(".env.development");
        }
    }
    async serverShutdown(): Promise<void> {}
}
- app.provider.ts [ 2.x ]
- app.ts [ 3.x ]
import { AppExpress } from "@expressots/adapter-express";
import {
    Env,
    IMiddleware,
    Middleware,
    ProviderManager,
} from "@expressots/core";
import { container } from "./app.container";
export class App extends AppExpress {
    private middleware: IMiddleware;
    private provider: ProviderManager;
    constructor() {
        super();
        this.middleware = container.get<IMiddleware>(Middleware);
        this.provider = container.get(ProviderManager);
    }
    protected configureServices(): void | Promise<void> {
        this.provider.register(Env);
        this.middleware.addBodyParser();
        this.middleware.setErrorHandler({ showStackTrace: true });
    }
    protected postServerInitialization(): void | Promise<void> {
        if (this.isDevelopment()) {
            this.provider.get(Env).checkAll();
        }
    }
    protected serverShutdown(): void | Promise<void> {}
}
import { AppExpress } from "@expressots/adapter-express";
import { AppContainer } from "@expressots/core";
import { AppModule } from "./app.module";
export class App extends AppExpress {
    private config: AppContainer = this.configContainer([AppModule]);
    async globalConfiguration(): Promise<void> {}
    async configureServices(): Promise<void> {
        this.Middleware.addBodyParser();
        this.Middleware.setErrorHandler({ showStackTrace: true });
    }
    async postServerInitialization(): Promise<void> {}
    async serverShutdown(): Promise<void> {}
}
Remove app.container.ts
In ExpressoTS 3.x, the app.container.ts file has been deprecated and is no longer needed. Module registration and dependency injection have been streamlined to be defined directly in the src\app.ts file. This simplifies the project structure and centralizes application configuration.
This recommendation applies to both opinionated and non-opinionated projects, ensuring consistency and reducing complexity regardless of the project type.
Below is the old implementation using app.container.ts for context and the new way to handle modules and environment configuration in src\app.ts.
- Opinionated
- Non-Opinionated
- app.container.ts [ 2.x ]
- app.ts [ 3.x ]
The previous approach required a separate app.container.ts file for setting up the application container and registering modules:
import { Container } from "inversify";
import { AppContainer } from "@expressots/core";
import { AppModule } from "@useCases/app/app.module";
export const appContainer: AppContainer = new AppContainer({
    autoBindInjectable: false,
});
export const container: Container = appContainer.create([
    // Add your modules here
    AppModule,
]);
In ExpressoTS 3.x, modules are now directly registered in the configContainer method of the App class. This method accepts an array of modules as its parameter, eliminating the need for a separate container file.
import { AppExpress } from "@expressots/adapter-express";
import { AppContainer, Env } from "@expressots/core";
import { AppModule } from "@useCases/app/app.module";
export class App extends AppExpress {
    // Register application modules
    private config: AppContainer = this.configContainer([AppModule]);
    // Global configurations such as routes and environment
    async globalConfiguration(): Promise<void> {
        this.setGlobalRoutePrefix("/v1");
        this.initEnvironment("development", {
            env: {
                development: ".env.development", // Path to the development environment file
                production: ".env.production", // Path to the production environment file
            },
        });
    }
    // Service-level configurations
    async configureServices(): Promise<void> {
        // Register environment-related dependencies
        this.Provider.register(Env);
        // Add global middleware configurations
        this.Middleware.addBodyParser(); // Parses incoming request bodies
        this.Middleware.setErrorHandler({ showStackTrace: true }); // Error handling middleware
    }
    // Post-server startup tasks
    async postServerInitialization(): Promise<void> {
        if (await this.isDevelopment()) {
            // Ensure environment variables are loaded correctly in development
            this.Provider.get(Env).checkFile(".env.development");
        }
    }
    // Graceful shutdown logic
    async serverShutdown(): Promise<void> {
        // Add any necessary shutdown procedures here
    }
}
- app.container.ts [ 2.x ]
- app.ts [ 3.x ]
The previous approach required a separate app.container.ts file for setting up the application container and registering modules:
import { AppContainer } from "@expressots/core";
import { AppModule } from "./app.module";
export const appContainer: AppContainer = new AppContainer({
    autoBindInjectable: false,
});
export const container = appContainer.create([
    // Add your modules here
    AppModule,
]);
In ExpressoTS 3.x, modules are now directly registered in the configContainer method of the App class. This method accepts an array of modules as its parameter, eliminating the need for a separate container file.
import { AppExpress } from "@expressots/adapter-express";
import { AppContainer } from "@expressots/core";
import { AppModule } from "./app.module";
export class App extends AppExpress {
    // Register application modules
    private config: AppContainer = this.configContainer([AppModule]);
    // Global configurations such as routes and environment
    async globalConfiguration(): Promise<void> {}
    // Service-level configurations
    async configureServices(): Promise<void> {
        this.Middleware.addBodyParser(); // Parses incoming request bodies
        this.Middleware.setErrorHandler({ showStackTrace: true }); // Error handling middleware
    }
    // Post-server startup tasks
    async postServerInitialization(): Promise<void> {}
    // Graceful shutdown logic
    async serverShutdown(): Promise<void> {}
}
Modules registration
Modules are registered using the configContainer method directly in the App class.
Add modules to the array in the configContainer method:
private config: AppContainer = this.configContainer([AppModule, AnotherModule]);
Replace AnotherModule with any additional modules you wish to register in your application.
Environment configuration
The initEnvironment method is used to initialize environment-specific configurations. Specify the paths to .env files for different environments (e.g., development and production).
this.initEnvironment("development", {
    env: {
        development: ".env.development",
        production: ".env.production",
    },
});
Use the Env provider to access environment variables defined in your .env files. These can be dynamically checked and validated during application initialization.
Update controllers
In version 3.x, dependency injection now requires the @inject decorator explicitly.
- app.controller.ts [ 2.x ]
- app.controller.ts [ 3.x ]
import { controller, Get } from "@expressots/adapter-express";
import { AppUseCase } from "./app.usecase";
@controller("/")
export class AppController {
    constructor(private appUseCase: AppUseCase) {} // Old change
    @Get("/")
    execute() {
        return this.appUseCase.execute();
    }
}
import { controller, Get } from "@expressots/adapter-express";
import { inject } from "@expressots/core"; // New import
import { AppUseCase } from "./app.usecase";
@controller("/")
export class AppController {
    @inject(AppUseCase) private appUseCase: AppUseCase; // New change
    @Get("/")
    execute() {
        return this.appUseCase.execute();
    }
}