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-metadatacan 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)inApplication’s constructor.app.instance("request", req)/response/nextduring a request (seeMiddlewareinjcc-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
- Framework constructor —
Applicationregistersapp,RouteServiceProvider,Event, etc. ExpressApplication— Bindsexpress,expressApp,httpListen,socketIo, etc.- Service providers — In
register(), callthis.app.bind(...)/singleton(...). Seeapp/Providers/AppServiceProvider.tsandbootstrap/providers.ts. - HTTP pipeline — Middleware puts
request,response,nexton the container for the current request. - 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:
emitDecoratorMetadatais enabled intsconfig.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
Applicationextends the container. JCC:Application→ExpressApplication→Container. - Registering services — Laravel:
AppServiceProvider::register. JCC: same idea inapp/Providers/*→register(). - Resolving — Laravel:
app(),resolve(). JCC:app.resolve(),app.make(). - Current HTTP request — Laravel: type-hint
Request. JCC:instance("request", req)(andresponse/next) per request.
Practical pattern in a provider
Elsewhere:
