JCC Express

Service Container

Introduction

The service container is a registry for creating and retrieving objects in your application. Instead of constructing dependencies by hand in every controller or route, you bind how something should be built once, then resolve it whenever you need it—similar in spirit to Laravel’s container.

In JCC Express MVC, the Application class is the container: it extends ExpressApplication, which extends Container from jcc-express-mvc. When bootstrap/app finishes building, you have a single app object that is both the Express app owner and the dependency injection hub.


Why use a container

  • One place to wire implementations (interfaces → concrete classes, factories, config).
  • Singletons for expensive or shared services (database, cache clients, queues).
  • Request-scoped values (request, response, next) registered per HTTP request and cleared when the response ends.
  • Constructor injection where reflect-metadata can resolve parameter types from the container (used in parts of the framework and advanced app code).

Core API

Bindings are keyed by string (for example "TestingService") or by class / constructor name when using make() / build().

bind(abstract, concrete, shared?)

Registers how to build a service. concrete may be:

  • A class — instantiated with optional constructor injection.
  • A factory function (container) => instance — you control creation.

If shared is true, the first resolved instance may be cached (singleton behavior for that binding).

singleton(abstract, concrete)

Ensures a binding exists and marks it shared so resolve returns the same instance afterward (typical for app-wide services).

instance(abstract, object)

Registers a ready-made object under a key. Used for:

  • app.instance("app", this) in Application’s constructor.
  • app.instance("request", req) / response / next during a request (see Middleware in jcc-express-mvc).

resolve(abstract, parameters?, callFactory?)

Returns an instance: checks instances map, aliases, then bindings, and may build a class with injected dependencies.

make(Class, params?)

Registers the class name if needed and resolves it—handy for on-the-fly resolution.

alias(alias, abstract)

Lets you resolve using an alternate name pointing at the same binding.

has(abstract)

Returns whether a binding, instance, or alias exists.

forget(abstract) / forgetMany(keys[])

Removes instances (and related alias keys). The framework uses forgetMany(["request", "response", "next"]) after each response finish so the next request does not reuse stale req/res.


Where bindings come from

  1. Framework constructor — Application registers app, RouteServiceProvider, Event, etc.
  2. ExpressApplication — Binds express, expressApp, httpListen, socketIo, etc.
  3. Service providers — In register(), call this.app.bind(...) / singleton(...). See app/Providers/AppServiceProvider.ts and bootstrap/providers.ts.
  4. HTTP pipeline — Middleware puts request, response, next on the container for the current request.
  5. Your code — Anywhere you have app, you can bind services at startup or (less often) at runtime.

Dependency injection on constructors

Container.build() uses reflect-metadata (design:paramtypes) to guess constructor parameters and resolve them by type name. This works when:

  • emitDecoratorMetadata is enabled in tsconfig.json (your project has it).
  • Parameter types are classes or names the container knows.

If resolution fails, you may need an explicit bind for that type or pass parameters into resolve.


Controllers and callControllerMethod

For controller actions, the framework can callControllerMethod, which reads inject:method:deps metadata and resolves FormRequest, models, route parameters, etc. Plain routes that use (req, res) => do not need this—you still benefit from view() / inertia() because they read response from the container after it is bound.


Global app and helpers

After globalHelpers(app) runs (during ApplicationBuilder.create()), globalThis.app is the Application instance. Helpers like env(), view(), inertia() use the container under the hood (for example response from app.resolve("response")).

Prefer constructor injection or explicit app.resolve() in providers and long-lived services; use globals where the framework already does (routes, Blade/Inertia helpers).


Comparison to Laravel (mental model)

  • Container class — Laravel: Illuminate\Container\Container. JCC: jcc-express-mvc/lib/Container.
  • Application — Laravel’s Application extends the container. JCC: ApplicationExpressApplicationContainer.
  • Registering services — Laravel: AppServiceProvider::register. JCC: same idea in app/Providers/*register().
  • Resolving — Laravel: app(), resolve(). JCC: app.resolve(), app.make().
  • Current HTTP request — Laravel: type-hint Request. JCC: instance("request", req) (and response / next) per request.

Practical pattern in a provider

TypeScript

Elsewhere:

TypeScript