Heximon Logo
Getting Started

Core Concepts

The six terms a Heximon app is built from — module, provider, constructor injection, controller, the build step, and the config line — each with a link to its dedicated page.

A whole Heximon app comes down to six terms. A module groups a feature. Providers are the plain classes inside it. Constructor injection wires those classes together. A controller turns HTTP requests into method calls.

A build step — the heximon() Vite plugin — reads all of that and generates the wiring before your app runs. And one config line registers that plugin. This page walks each term once; every section links to the page that goes deep.

Group a feature with a module

A module is a class that extends Module({...}). It draws the boundary of a feature — what it owns, what it pulls in from other modules — and it's the only place the compiler looks to find your classes.

src/users/users.module.ts
import { Module } from "@heximon/runtime";
import { UsersController } from "./users.controller";
import { UsersRepository } from "./users.repository";

export class UsersModule extends Module({
  providers: [UsersRepository],
  http: { controllers: [UsersController] },
  exports: [UsersRepository],
}) {}

providers and exports are structural fields the kernel always understands; http is a plugin's namespace for the concept classes it owns — here, a controller. That keeps registration explicit: a class is wired because it's listed here, never because it was scanned off disk. → Modules & DI

Inject providers by their constructor

A provider is any class the graph can build. List it in providers and it's injectable — no decorator needed. Its dependencies are its constructor parameters: declare one typed by the class you want, and the compiler resolves it by identity and passes the instance in.

src/users/users.controller.ts
export class UsersController implements Controller<"/users"> {
  // resolved by class identity — no token, no decorator
  constructor(private readonly users: UsersRepository) {}
}

That's constructor injection: no token to declare, no string key to drift, just the type in the signature. → Modules & DI

Turn requests into method calls with a controller

A controller declares implements Controller<"/prefix"> and is listed under http: { controllers }. Each route is declared by its handler's action parameter typeGet<"/:id"> is the route, so there's no separate config to keep in sync with the method.

src/users/users.controller.ts
import type { Controller, Get } from "@heximon/http";
import { type User, UsersRepository } from "./users.repository";

export class UsersController implements Controller<"/users"> {
  constructor(private readonly users: UsersRepository) {}

  public async get(action: Get<"/:id">): Promise<User | undefined> {
    return this.users.findById(action.request.pathParams.id); // pathParams.id typed from "/:id"
  }
}

Controllers

Let the build step write the wiring

The defining move: dependency injection happens at build time, not at runtime. The heximon() Vite plugin reads your modules and constructors and emits plain JavaScript that constructs everything directly (new UsersController(new UsersRepository())) into .heximon/.

There's no container, no reflection, and no entry file to write — the plugin generates the fetch boot, and pnpm dev serves it. Because the compiler reads source rather than running it, your providers and imports lists must be static (no computed or conditional imports) — and in exchange the wiring is ordinary code you can open and read.

pnpm dev
# compiles src/ to wiring, serves it, and recompiles on every change

Context & Lifecycle covers what runs once the wiring boots (request context, OnInit, graceful shutdown).

Register it with one config line

The build step is a plugin you install like any other Vite plugin — it takes no arguments of its own; the heximon.config.ts config file lists which Heximon plugins are active (here, just HTTP):

heximon.config.ts
import { defineHeximonConfig } from "@heximon/build";
import { HttpPlugin } from "@heximon/http/compiler";

export default defineHeximonConfig({ plugins: [new HttpPlugin()] });
vite.config.ts
import heximon from "@heximon/build/vite";
import { defineConfig } from "vite-plus";

export default defineConfig({
  plugins: [heximon()], // takes no arguments — plugins live in heximon.config.ts above
});

That's the whole setup: one config file, one line in vite.config.ts. Every capability past this — validation, contracts, a database, background jobs, events, CQRS — is the same move, repeated: add a package, add its namespace key to a module, register its plugin. See Where next? for the full map.

The Nitro and Nuxt hosts take the sameheximon.config.ts, read through their own module instead of the heximon() Vite plugin — so moving from the dev server to a deployment is a config swap, not a rewrite. See Deploy targets.

Next steps

Copyright © 2026