JCC Express

Request Lifecycle

Introduction

When you use any tool in the real world, you work with more confidence when you understand how that tool works. Building applications is no different. When you know how your framework handles an HTTP request, you feel more comfortable choosing middleware, debugging routes, and structuring app/ and route/.

This document gives a high-level picture of how JCC Express MVC works: Express.js underneath, with a Laravel-inspired service container, HTTP kernel, service providers, and routing.


Lifecycle overview

At the highest level:

text

First steps

The entry point

The entry point for the HTTP application is server.ts at the project root (not public/index.php like PHP Laravel—your reverse proxy forwards to the Node/Bun process). server.ts is small: it imports the application from bootstrap/app and calls app.run().

TypeScript

Loading the application

bootstrap/app.ts builds an Application instance through Application.configuration()—a builder that wires routing, app/Config, service providers, app/Http/kernel, and global middleware, then .create(). That returns the same app you import in server.ts.

The first structural object the framework gives you is this Application: it acts as the service container (bind / resolve services) and wraps the Express app used for HTTP.


HTTP kernel

In Laravel, the HTTP kernel is Illuminate\Foundation\Http\Kernel. In JCC Express MVC, the equivalent is app/Http/kernel.ts: a class registered as HttpKernel whose middlewares array becomes part of the global Express pipeline.

The kernel is the central place through which every web request passes after the framework’s own Express setup (body parsers, session, CORS, logging, rate limiting, HttpRequest / HttpResponse, etc.) and before your routes run.

The kernel also declares middlewareAliases—short names like auth, guest, or throttles—that you attach per route with Route.middleware(['auth']).

Think of the stack as: framework defaults → your kernel middlewares (for example CSRF, method spoofing, Inertia) → route matching → route middleware → handler. The kernel’s job is to define that global slice and the alias map; the Middleware class inside jcc-express-mvc applies the full chain to Express when the app is built.


Service providers

One of the most important bootstrap steps is loading service providers. Providers register bindings on the container (register) and run extra setup once registrations exist (boot).

In ApplicationBuilder.withProviders(), the framework prepends DatabaseServiceProvider, registers your list from bootstrap/providers.ts (and may place AuthServiceProvider early), then appends QueueServiceProvider. That order matters so the database, auth, and queue subsystems are available where other code expects them.

Your own providers live under app/Providers/ (for example AppServiceProvider) and are listed in bootstrap/providers.ts. Use AppServiceProvider (or additional providers) for container bindings, config-driven setup, and small bootstrap tasks—same idea as Laravel’s AppServiceProvider.

Most “big” features (database, queues, routing registration, events) are tied to framework or app providers. Understanding register vs boot helps when something is “not bound yet” during startup.


Routing

After the application is built and app.run() executes:

  1. RouteBuilder receives the service container.
  2. RouteServiceProvider.loadRoutes() runs each configured route module (for example route/web, route/api) with the right URL prefix from bootstrap/app.ts. Route files call Route.get, Route.post, etc., which record routes in RouteBuilder—they do not yet attach to Express in all setups until the next step.
  3. Server opens PORT, then calls RouteBuilder.registerRoute(expressApp), which registers method + path + middleware + handler on Express and wires global error handling.

So: providers and kernel configure the app; route files declare endpoints; listening is when those endpoints are mounted on Express.

When a request arrives, Express dispatches it to the matching route. The handler may be a closure or a controller method resolved from the container.


Middleware

Middleware filters or inspects the request (and sometimes the response path). Examples:

  • Global middleware—applied to every request—includes the framework stack (JSON, session, CORS, …) plus Kernel.middlewares (CSRF, Inertia, …).
  • Route middleware—only on routes that name it—uses Kernel.middlewareAliases (auth, guest, …).

If the request fails a guard (for example user not authenticated), middleware can redirect or abort before your controller runs.

If the request passes all matched middleware, your route or controller runs and produces the response (JSON, Blade, Inertia, redirect, …).

Reference — framework middleware order (before kernel entries), from jcc-express-mvc/lib/Middleware:

  1. express.json()
  2. express.urlencoded({ extended: false })
  3. cookie-parser()
  4. express-session
  5. connect-flash
  6. express-fileupload
  7. CORS (app.config.cors)
  8. Morgan (format depends on APP_DEBUG)
  9. Rate limit (app.config.rateLimit)
  10. HttpRequest / HttpResponse and jccSession on req

Then Kernel.middlewares runs in array order. After that, middleware binds request, response, and next on the container for helpers like view() and inertia().

Inertia: visits with the X-Inertia header receive a JSON page payload; full page loads get HTML from the root Blade view. The same route handler often serves both; Inertia middleware and res.inertia handle the difference.


Finishing up

When your controller or closure finishes—by calling res.send, res.json, res.render, view(), res.inertia, inertia(), redirect(), res.inertiaRedirect(), or returning a value the route wrapper turns into JSON—Express ends the response.

The framework listens for finish on the response and clears the per-request request / response / next bindings on the container so the next request does not leak state.

If an error is passed to next(error) or thrown without being caught, AppErrorHandler maps it to an HTTP response (status and body) according to framework rules.


Focus on service providers

Service providers are the main lever for bootstrapping a JCC Express MVC application: the Application is created, providers register and boot, routes are loaded and registered, then requests hit the kernel and router.

Your bootstrap/providers.ts list is the counterpart to Laravel’s provider list—keep it focused: put general bindings in AppServiceProvider, and add more providers when a feature needs its own register/boot block.


Where to customize (quick reference)

  • Global HTTP middleware — app/Http/kernel.tsmiddlewares and middlewareAliases.
  • Default Express stack — jcc-express-mvc/lib/Middleware + app/Config (CORS, rate limit, engine).
  • Routes and API prefixes — route/*.ts and bootstrap/app.tswithRouting.
  • Container bindings and boot logic — app/Providers/* and bootstrap/providers.ts.