Skip to main content
Version: 3.0.0

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.

import { ExpressoConfig, Pattern } from "@expressots/core";

const config: ExpressoConfig = {
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.

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();

File env.ts

Environment management has been simplified and made more dynamic in version 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;

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.

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 {}
}

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.

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,
]);

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.

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();
}
}