Skip to main content
Version: 4.0.0-preview

First steps

This page walks you from a clean machine to a running ExpressoTS v4 server. Every command and file shown here matches what expressots new actually produces: the exact same scaffolds the framework's own validation suite runs against.

Architecture

ExpressoTS centers on a DI container that wires controllers, use cases, providers, and repositories through modules. Incoming requests flow through controllers, which delegate to use cases and external providers as needed.

ExpressoTS application architecture overview
  • DTO IN/OUT: Structure for incoming and outgoing request data.
  • Controller: Handles HTTP routing and request processing.
  • Use Case: Executes application-specific business logic.
  • Provider: Supplies external capabilities such as database access.
  • Repository: Manages direct persistence communication.

Prerequisites

  • Node.js >= 20.18.0 (LTS or newer).
  • npm, yarn, pnpm, or bun.
  • Git (the CLI uses degit to fetch templates from GitHub).

Verify your version:

node --version
# v20.18.0 or higher

Install the CLI

npm i -g @expressots/cli

While v4 is in preview, install the next channel: npm i -g @expressots/cli@next.

Confirm the install:

expressots --help

Pick a template

ExpressoTS v4 ships four first-class templates. Pick the one that matches your use case: you can change later, nothing is locked in.

TemplateWhen to use itGenerated by
applicationThe default REST/GraphQL API. Full DI container, controllers, middleware presets, lifecycle hooks.expressots new <name> -t application -p npm (defaults to -s api when omitted)
application-with-eventsSame as application plus the type-safe Event Bus example wired in.expressots new <name> -t application -p npm -e (defaults to -s api when omitted)
microSingle-file lightweight HTTP service, no DI container. Ideal for serverless functions and tiny gateways.expressots new <name> -t micro -p npm
providerA reusable @expressots/*-style npm package. Dual ESM + CJS build, ready to publish.expressots create --provider <name>
Flags vs interactive mode
  • Silent (CI-friendly): pass both -t and -p together. Example: expressots new my-api -t application -p npm -s api.
  • Preset default: when -s is omitted in silent mode with the application template, the CLI defaults to -s api (the recommended preset). You can still pass any other preset explicitly, e.g. -s graphql.
  • Interactive: run expressots new my-api with no -t or -p. The CLI prompts for template, package manager, preset, and whether to include events.
  • Events: -e is a boolean switch only (no value). Use -e or --events to scaffold application-with-events. Do not pass -e interactive; that string is parsed as a positional argument and fails validation.

The ex command is an alias for expressots (same binary).

When you scaffold an application (or application-with-events), the CLI also asks for a middleware preset:

Preset (-s flag)What it includes
api (default)parse + logger + security (helmet) + compress + rate limit. The recipe behind Middleware.applyPreset("api").
webThe api preset plus cookies and session support.
graphqlTuned for GraphQL: parse + helmet + compression + an Apollo Server placeholder.
microserviceMinimal request parsing + compression for service-to-service traffic.
minimalJust parse(). You wire the rest yourself.

Scaffold a project

expressots new my-api -t application -p npm -s api
cd my-api
npm run dev
my-api/
├── src/
│ ├── main.ts # loadEnvSync() + bootstrap(App)
│ ├── app.ts # AppExpress class with all four lifecycle hooks
│ └── app.controller.ts # Welcome + /health route
├── test/
│ └── app.controller.spec.ts # createTestApp + fluent request DSL
├── .env # Copy of .env.example (PORT, NODE_ENV, LOG_LEVEL, …)
├── .env.example
├── expressots.config.ts # CLI scaffolding config (Pattern + scaffoldSchematics)
├── jest.config.ts
├── tsconfig.json
├── tsconfig.build.json
└── package.json

Run it

Once npm run dev is up, you'll see the ExpressoTS startup banner:

ExpressoTS v4.0.0 my-api v1.0.0 Node v22.x (linux)

⚡ Server ⚙️ Config 💚 Health
Env: development Prefix: /api Memory: 42 MB
Port: 3000 Node Version: v22.x Heap: 54 %

📊 Metrics 🛡️ Security ⏱️ Startup
Routes: 2 Interceptors: 0 Time: 36 ms
Controllers: 1 URL: localhost:3000
Providers: 1
Middleware: 7

Hit the routes:

curl http://localhost:3000/api/
# {"message":"Hello from ExpressoTS v4!","docs":"https://expresso-ts.com/docs/"}

curl http://localhost:3000/api/health
# {"status":"ok","uptime":4.46}

The global prefix /api comes from the template's globalConfiguration() hook (this.setGlobalRoutePrefix("/api")). Remove that line to expose routes at /.

The micro template skips the banner and global prefix: routes live at http://localhost:3000/.

What bootstrap() and loadEnvSync() do

src/main.ts in every application variant looks like this:

import { bootstrap, loadEnvSync } from "@expressots/core";
import { App } from "./app";

loadEnvSync();
void bootstrap(App);
  • loadEnvSync() is the recommended way to load .env files. Call it before bootstrap() so process.env is fully populated before any config or DI binding runs. By default it loads .env, then layers .env.${NODE_ENV} and the matching .local overrides on top: no arguments needed.
  • bootstrap(App) instantiates the AppExpress subclass, runs the lifecycle hooks (globalConfigurationconfigureServicespostServerInitialization), starts the HTTP server, and wires up graceful shutdown on SIGINT / SIGTERM.

If you don't call loadEnvSync() and don't pass an envFileConfig option to bootstrap(), no .env files are read: only the variables already in process.env (the deployment platform, Docker secrets, CI runner, etc.). This is intentional: it keeps containerized and CI deployments fast and predictable. See Bootstrap → Environment file loading for the full opt-in matrix.

What app.ts does

src/app.ts extends AppExpress and overrides the four lifecycle hooks. The scaffold pre-fills the most common pieces:

import { AppExpress } from "@expressots/adapter-express";
import { AppContainer, CreateModule } from "@expressots/core";
import { AppController } from "./app.controller";

export class App extends AppExpress {
private readonly container: AppContainer = this.configContainer([
CreateModule([AppController]),
]);

globalConfiguration(): void {
this.setGlobalRoutePrefix("/api");
}

async configureServices(): Promise<void> {
this.Middleware.applyPreset("api"); // <- replaced by your -s preset
}

async postServerInitialization(): Promise<void> {}

async serverShutdown(): Promise<void> {}
}
HookWhen it runsTypical use
globalConfigurationOnce, before DI is builtsetGlobalRoutePrefix, banner customisation
configureServicesOnce, after DI is built, before listen()Middleware.applyPreset(…), setupInterceptorsForExpress(…), setupEventSystemForExpress(…), setupAuthorizationForExpress(…)
postServerInitializationOnce, after listen() resolveswarm caches, log readiness, register health probes
serverShutdownOn SIGINT / SIGTERMdrain queues, close DB connections, flush logs

See Lifecycle Hooks for a deep dive.

Daily commands

CommandWhat it does
npm run devStart dev server (tsx --watch, Studio agent enabled).
npm run buildCompile to dist/, rewrite path aliases.
npm run prodRun the compiled output with node.
npm testJest, single worker (--runInBand).
npm run test:covJest with coverage.
npm run lintESLint with --fix.
npm run formatPrettier write.
npm run studioLaunch ExpressoTS Studio.

Where to next

  • Bootstrap: every option bootstrap() accepts, plus the env-file flow.
  • Lifecycle Hooks: the four hooks plus provider-level IBootstrap / IShutdown.
  • Configuration: defineConfig + Env.* builders for typed env vars.
  • Middleware: built-in presets, custom middleware, profiler.
  • Testing: createTestApp, fluent HTTP, snapshots, custom matchers.
  • Studio: the in-development developer experience platform.

Support the project

ExpressoTS is MIT-licensed open source. If it saves you time, please support the project.