jcc-express-mvc 1.9.3 → 1.9.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/final-documentation/Architecture Concept/Request-Lifecycle.md +137 -0
- package/final-documentation/Architecture Concept/Service-Container.md +134 -0
- package/final-documentation/Architecture Concept/Service-Provider.md +105 -0
- package/final-documentation/Database/Database-Introduction.md +240 -0
- package/final-documentation/Database/Migrations.md +319 -0
- package/final-documentation/Database/Mongoose.md +125 -0
- package/final-documentation/Database/Query-Builder.md +181 -0
- package/final-documentation/Database/Seeding.md +93 -0
- package/final-documentation/Database/Sequelize.md +147 -0
- package/final-documentation/Database/Transactions.md +81 -0
- package/final-documentation/Digging Deeper/Broadcasting.md +145 -0
- package/final-documentation/Digging Deeper/Cache.md +154 -0
- package/final-documentation/Digging Deeper/Carbon.md +220 -0
- package/final-documentation/Digging Deeper/Deferred-Functions.md +139 -0
- package/final-documentation/Digging Deeper/Events.md +123 -0
- package/final-documentation/Digging Deeper/File-Storage.md +160 -0
- package/final-documentation/Digging Deeper/HTTP-Client.md +112 -0
- package/final-documentation/Digging Deeper/Helpers.md +111 -0
- package/final-documentation/Digging Deeper/Logging.md +199 -0
- package/final-documentation/Digging Deeper/Mail.md +118 -0
- package/final-documentation/Digging Deeper/Monitor.md +871 -0
- package/final-documentation/Digging Deeper/Queues.md +175 -0
- package/final-documentation/Digging Deeper/Rate-Limiting.md +83 -0
- package/final-documentation/Digging Deeper/Strings.md +115 -0
- package/final-documentation/Digging Deeper/Task-Scheduling.md +411 -0
- package/final-documentation/Getting-Started/Configuration.md +104 -0
- package/final-documentation/Getting-Started/Deployment.md +191 -0
- package/final-documentation/Getting-Started/Directory-structure.md +121 -0
- package/final-documentation/Getting-Started/Frontend.md +183 -0
- package/final-documentation/Getting-Started/Installation.md +309 -0
- package/final-documentation/Getting-Started/Introduction.md +27 -0
- package/final-documentation/JCC-Eloquent/Defining-Model.md +297 -0
- package/final-documentation/JCC-Eloquent/JCC-Eloquent-Introduction.md +134 -0
- package/final-documentation/JCC-Eloquent/Mutators.md +95 -0
- package/final-documentation/JCC-Eloquent/Observer.md +10 -0
- package/final-documentation/JCC-Eloquent/Pagination.md +137 -0
- package/final-documentation/JCC-Eloquent/Query-Builder.md +346 -0
- package/final-documentation/JCC-Eloquent/Relationships.md +113 -0
- package/final-documentation/JCC-Eloquent/Retrieving-Models.md +146 -0
- package/final-documentation/JCC-Eloquent/Scopes.md +88 -0
- package/final-documentation/JCC-Eloquent/SoftDelete.md +92 -0
- package/final-documentation/Packages/Cloudinary.md +85 -0
- package/final-documentation/Packages/JWT.md +91 -0
- package/final-documentation/Packages/Socialite.md +153 -0
- package/final-documentation/Security/Authentication.md +132 -0
- package/final-documentation/Security/Authorization.md +117 -0
- package/final-documentation/Security/Email-Verification.md +80 -0
- package/final-documentation/Security/Encryption.md +105 -0
- package/final-documentation/Security/Hashing.md +84 -0
- package/final-documentation/Testing/Database-Testing.md +109 -0
- package/final-documentation/Testing/Feature-Testing.md +249 -0
- package/final-documentation/Testing/Introduction.md +101 -0
- package/final-documentation/Testing/Testing-Overview.md +63 -0
- package/final-documentation/Testing/Unit-Testing.md +118 -0
- package/final-documentation/The Basics/Artisan-Node.md +123 -0
- package/final-documentation/The Basics/Asset-bundling.md +113 -0
- package/final-documentation/The Basics/CSRF-protection.md +142 -0
- package/final-documentation/The Basics/Controllers.md +96 -0
- package/final-documentation/The Basics/Error-handling.md +102 -0
- package/final-documentation/The Basics/JCC-Blade.md +330 -0
- package/final-documentation/The Basics/Middleware.md +174 -0
- package/final-documentation/The Basics/Request.md +178 -0
- package/final-documentation/The Basics/Response.md +58 -0
- package/final-documentation/The Basics/Routing.md +322 -0
- package/final-documentation/The Basics/Session.md +97 -0
- package/final-documentation/The Basics/Validation.md +235 -0
- package/final-documentation/The Basics/Views.md +87 -0
- package/lib/Monitor/Views/{cache.blade.html → JSBlade/cache.blade.html} +1 -1
- package/lib/Monitor/Views/{commands.blade.html → JSBlade/commands.blade.html} +2 -2
- package/lib/Monitor/Views/{mail.blade.html → JSBlade/mail.blade.html} +1 -1
- package/lib/Monitor/Views/{outgoing.blade.html → JSBlade/outgoing.blade.html} +1 -1
- package/lib/Monitor/Views/{queries.blade.html → JSBlade/queries.blade.html} +6 -0
- package/lib/Monitor/Views/{requests.blade.html → JSBlade/requests.blade.html} +13 -3
- package/lib/Monitor/Views/{scheduled.blade.html → JSBlade/scheduled.blade.html} +1 -1
- package/lib/Monitor/Views/React/Cache.jsx +29 -0
- package/lib/Monitor/Views/React/Commands.jsx +28 -0
- package/lib/Monitor/Views/React/Dashboard.jsx +72 -0
- package/lib/Monitor/Views/React/Exceptions.jsx +38 -0
- package/lib/Monitor/Views/React/Jobs.jsx +29 -0
- package/lib/Monitor/Views/React/Logs.jsx +54 -0
- package/lib/Monitor/Views/React/Mail.jsx +29 -0
- package/lib/Monitor/Views/React/MonitorLayout.jsx +94 -0
- package/lib/Monitor/Views/React/Notifications.jsx +28 -0
- package/lib/Monitor/Views/React/Outgoing.jsx +28 -0
- package/lib/Monitor/Views/React/Queries.jsx +70 -0
- package/lib/Monitor/Views/React/Requests.jsx +34 -0
- package/lib/Monitor/Views/React/Scheduled.jsx +28 -0
- package/lib/Monitor/Views/React/Sidebar.jsx +108 -0
- package/lib/Monitor/Views/React/ui.jsx +176 -0
- package/lib/Monitor/Views/React/useTheme.js +69 -0
- package/lib/Monitor/Views/Vue/Cache.vue +38 -0
- package/lib/Monitor/Views/Vue/Commands.vue +37 -0
- package/lib/Monitor/Views/Vue/Dashboard.vue +87 -0
- package/lib/Monitor/Views/Vue/DataTable.vue +116 -0
- package/lib/Monitor/Views/Vue/EmptyState.vue +18 -0
- package/lib/Monitor/Views/Vue/Exceptions.vue +43 -0
- package/lib/Monitor/Views/Vue/Jobs.vue +38 -0
- package/lib/Monitor/Views/Vue/Logs.vue +58 -0
- package/lib/Monitor/Views/Vue/Mail.vue +38 -0
- package/lib/Monitor/Views/Vue/MethodBadge.vue +8 -0
- package/lib/Monitor/Views/Vue/MonitorLayout.vue +80 -0
- package/lib/Monitor/Views/Vue/Notifications.vue +37 -0
- package/lib/Monitor/Views/Vue/Outgoing.vue +38 -0
- package/lib/Monitor/Views/Vue/Panel.vue +18 -0
- package/lib/Monitor/Views/Vue/Queries.vue +68 -0
- package/lib/Monitor/Views/Vue/Requests.vue +49 -0
- package/lib/Monitor/Views/Vue/Scheduled.vue +37 -0
- package/lib/Monitor/Views/Vue/Sidebar.vue +98 -0
- package/lib/Monitor/Views/Vue/StateBadge.vue +25 -0
- package/lib/Monitor/Views/Vue/StatusBadge.vue +17 -0
- package/lib/Monitor/Views/Vue/format.js +31 -0
- package/lib/Monitor/Views/Vue/useTheme.js +71 -0
- package/package.json +1 -1
- /package/lib/Monitor/Views/{dashboard.blade.html → JSBlade/dashboard.blade.html} +0 -0
- /package/lib/Monitor/Views/{exceptions.blade.html → JSBlade/exceptions.blade.html} +0 -0
- /package/lib/Monitor/Views/{jobs.blade.html → JSBlade/jobs.blade.html} +0 -0
- /package/lib/Monitor/Views/{logs.blade.html → JSBlade/logs.blade.html} +0 -0
- /package/lib/Monitor/Views/{notifications.blade.html → JSBlade/notifications.blade.html} +0 -0
- /package/lib/Monitor/Views/{partials → JSBlade/partials}/empty-state.blade.html +0 -0
- /package/lib/Monitor/Views/{partials → JSBlade/partials}/sidebar.blade.html +0 -0
- /package/lib/Monitor/Views/{partials → JSBlade/partials}/top-header.blade.html +0 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# Request lifecycle
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
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/`.
|
|
6
|
+
|
|
7
|
+
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.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Lifecycle overview
|
|
12
|
+
|
|
13
|
+
At the highest level:
|
|
14
|
+
|
|
15
|
+
```text
|
|
16
|
+
Web server / reverse proxy
|
|
17
|
+
→ Node runs server.ts
|
|
18
|
+
→ bootstrap/app builds the Application (container + config + providers + kernel + middleware)
|
|
19
|
+
→ app.run() loads routes into RouteBuilder, then listens on PORT
|
|
20
|
+
→ Each request: Express middleware stack → kernel middleware → container binds req/res
|
|
21
|
+
→ Router matches route → route middleware → controller or closure → response
|
|
22
|
+
→ Response finishes; per-request container bindings are cleared
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## First steps
|
|
28
|
+
|
|
29
|
+
### The entry point
|
|
30
|
+
|
|
31
|
+
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()`.
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { app } from "./bootstrap/app";
|
|
35
|
+
app.run();
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Loading the application
|
|
39
|
+
|
|
40
|
+
`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`.
|
|
41
|
+
|
|
42
|
+
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.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## HTTP kernel
|
|
47
|
+
|
|
48
|
+
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.
|
|
49
|
+
|
|
50
|
+
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.
|
|
51
|
+
|
|
52
|
+
The kernel also declares `middlewareAliases`—short names like `auth`, `guest`, or throttles—that you attach per route with `Route.middleware(['auth'])`.
|
|
53
|
+
|
|
54
|
+
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.
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Service providers
|
|
59
|
+
|
|
60
|
+
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`).
|
|
61
|
+
|
|
62
|
+
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.
|
|
63
|
+
|
|
64
|
+
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`.
|
|
65
|
+
|
|
66
|
+
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.
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Routing
|
|
71
|
+
|
|
72
|
+
After the application is built and `app.run()` executes:
|
|
73
|
+
|
|
74
|
+
1. `RouteBuilder` receives the service container.
|
|
75
|
+
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.
|
|
76
|
+
3. `Server` opens `PORT`, then calls `RouteBuilder.registerRoute(expressApp)`, which registers method + path + middleware + handler on Express and wires global error handling.
|
|
77
|
+
|
|
78
|
+
So: providers and kernel configure the app; route files declare endpoints; listening is when those endpoints are mounted on Express.
|
|
79
|
+
|
|
80
|
+
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.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Middleware
|
|
85
|
+
|
|
86
|
+
Middleware filters or inspects the request (and sometimes the response path). Examples:
|
|
87
|
+
|
|
88
|
+
- Global middleware—applied to every request—includes the framework stack (JSON, session, CORS, …) plus `Kernel.middlewares` (CSRF, Inertia, …).
|
|
89
|
+
- Route middleware—only on routes that name it—uses `Kernel.middlewareAliases` (`auth`, `guest`, …).
|
|
90
|
+
|
|
91
|
+
If the request fails a guard (for example user not authenticated), middleware can redirect or abort before your controller runs.
|
|
92
|
+
|
|
93
|
+
If the request passes all matched middleware, your route or controller runs and produces the response (JSON, Blade, Inertia, redirect, …).
|
|
94
|
+
|
|
95
|
+
Reference — framework middleware order (before kernel entries), from `jcc-express-mvc/lib/Middleware`:
|
|
96
|
+
|
|
97
|
+
1. `express.json()`
|
|
98
|
+
2. `express.urlencoded({ extended: false })`
|
|
99
|
+
3. `cookie-parser()`
|
|
100
|
+
4. `express-session`
|
|
101
|
+
5. `connect-flash`
|
|
102
|
+
6. `express-fileupload`
|
|
103
|
+
7. CORS (`app.config.cors`)
|
|
104
|
+
8. Morgan (format depends on `APP_DEBUG`)
|
|
105
|
+
9. Rate limit (`app.config.rateLimit`)
|
|
106
|
+
10. `HttpRequest` / `HttpResponse` and `jccSession` on `req`
|
|
107
|
+
|
|
108
|
+
Then `Kernel.middlewares` runs in array order. After that, middleware binds `request`, `response`, and `next` on the container for helpers like `view()` and `inertia()`.
|
|
109
|
+
|
|
110
|
+
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.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Finishing up
|
|
115
|
+
|
|
116
|
+
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.
|
|
117
|
+
|
|
118
|
+
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.
|
|
119
|
+
|
|
120
|
+
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.
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Focus on service providers
|
|
125
|
+
|
|
126
|
+
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.
|
|
127
|
+
|
|
128
|
+
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.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## Where to customize (quick reference)
|
|
133
|
+
|
|
134
|
+
- Global HTTP middleware — `app/Http/kernel.ts` → `middlewares` and `middlewareAliases`.
|
|
135
|
+
- Default Express stack — `jcc-express-mvc/lib/Middleware` + `app/Config` (CORS, rate limit, engine).
|
|
136
|
+
- Routes and API prefixes — `route/*.ts` and `bootstrap/app.ts` → `withRouting`.
|
|
137
|
+
- Container bindings and boot logic — `app/Providers/*` and `bootstrap/providers.ts`.
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# Service container
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
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.
|
|
6
|
+
|
|
7
|
+
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.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Why use a container
|
|
12
|
+
|
|
13
|
+
- One place to wire implementations (interfaces → concrete classes, factories, config).
|
|
14
|
+
- Singletons for expensive or shared services (database, cache clients, queues).
|
|
15
|
+
- Request-scoped values (`request`, `response`, `next`) registered per HTTP request and cleared when the response ends.
|
|
16
|
+
- Constructor injection where `reflect-metadata` can resolve parameter types from the container (used in parts of the framework and advanced app code).
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
## Core API
|
|
21
|
+
|
|
22
|
+
Bindings are keyed by string (for example `"TestingService"`) or by class / constructor name when using `make()` / `build()`.
|
|
23
|
+
|
|
24
|
+
### `bind(abstract, concrete, shared?)`
|
|
25
|
+
|
|
26
|
+
Registers how to build a service. `concrete` may be:
|
|
27
|
+
|
|
28
|
+
- A class — instantiated with optional constructor injection.
|
|
29
|
+
- A factory function `(container) => instance` — you control creation.
|
|
30
|
+
|
|
31
|
+
If `shared` is `true`, the first resolved instance may be cached (singleton behavior for that binding).
|
|
32
|
+
|
|
33
|
+
### `singleton(abstract, concrete)`
|
|
34
|
+
|
|
35
|
+
Ensures a binding exists and marks it shared so `resolve` returns the same instance afterward (typical for app-wide services).
|
|
36
|
+
|
|
37
|
+
### `instance(abstract, object)`
|
|
38
|
+
|
|
39
|
+
Registers a ready-made object under a key. Used for:
|
|
40
|
+
|
|
41
|
+
- `app.instance("app", this)` in `Application`’s constructor.
|
|
42
|
+
- `app.instance("request", req)` / `response` / `next` during a request (see `Middleware` in `jcc-express-mvc`).
|
|
43
|
+
|
|
44
|
+
### `resolve(abstract, parameters?, callFactory?)`
|
|
45
|
+
|
|
46
|
+
Returns an instance: checks instances map, aliases, then bindings, and may `build` a class with injected dependencies.
|
|
47
|
+
|
|
48
|
+
### `make(Class, params?)`
|
|
49
|
+
|
|
50
|
+
Registers the class name if needed and `resolve`s it—handy for on-the-fly resolution.
|
|
51
|
+
|
|
52
|
+
### `alias(alias, abstract)`
|
|
53
|
+
|
|
54
|
+
Lets you `resolve` using an alternate name pointing at the same binding.
|
|
55
|
+
|
|
56
|
+
### `has(abstract)`
|
|
57
|
+
|
|
58
|
+
Returns whether a binding, instance, or alias exists.
|
|
59
|
+
|
|
60
|
+
### `forget(abstract)` / `forgetMany(keys[])`
|
|
61
|
+
|
|
62
|
+
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`.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Where bindings come from
|
|
67
|
+
|
|
68
|
+
1. Framework constructor — `Application` registers `app`, `RouteServiceProvider`, `Event`, etc.
|
|
69
|
+
2. `ExpressApplication` — Binds `express`, `expressApp`, `httpListen`, `socketIo`, etc.
|
|
70
|
+
3. Service providers — In `register()`, call `this.app.bind(...)` / `singleton(...)`. See `app/Providers/AppServiceProvider.ts` and `bootstrap/providers.ts`.
|
|
71
|
+
4. HTTP pipeline — Middleware puts `request`, `response`, `next` on the container for the current request.
|
|
72
|
+
5. Your code — Anywhere you have `app`, you can bind services at startup or (less often) at runtime.
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Dependency injection on constructors
|
|
77
|
+
|
|
78
|
+
`Container.build()` uses `reflect-metadata` (`design:paramtypes`) to guess constructor parameters and `resolve` them by type name. This works when:
|
|
79
|
+
|
|
80
|
+
- `emitDecoratorMetadata` is enabled in `tsconfig.json` (your project has it).
|
|
81
|
+
- Parameter types are classes or names the container knows.
|
|
82
|
+
|
|
83
|
+
If resolution fails, you may need an explicit `bind` for that type or pass parameters into `resolve`.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Controllers and `callControllerMethod`
|
|
88
|
+
|
|
89
|
+
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.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Global `app` and helpers
|
|
94
|
+
|
|
95
|
+
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")`).
|
|
96
|
+
|
|
97
|
+
Prefer constructor injection or explicit `app.resolve()` in providers and long-lived services; use globals where the framework already does (routes, Blade/Inertia helpers).
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Comparison to Laravel (mental model)
|
|
102
|
+
|
|
103
|
+
- Container class — Laravel: `Illuminate\Container\Container`. JCC: `jcc-express-mvc/lib/Container`.
|
|
104
|
+
- Application — Laravel’s `Application` extends the container. JCC: `Application` → `ExpressApplication` → `Container`.
|
|
105
|
+
- Registering services — Laravel: `AppServiceProvider::register`. JCC: same idea in `app/Providers/*` → `register()`.
|
|
106
|
+
- Resolving — Laravel: `app()`, `resolve()`. JCC: `app.resolve()`, `app.make()`.
|
|
107
|
+
- Current HTTP request — Laravel: type-hint `Request`. JCC: `instance("request", req)` (and `response` / `next`) per request.
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## Practical pattern in a provider
|
|
112
|
+
|
|
113
|
+
```typescript
|
|
114
|
+
// app/Providers/AppServiceProvider.ts (conceptual)
|
|
115
|
+
import { ServiceProvider } from "jcc-express-mvc/Core/Provider";
|
|
116
|
+
|
|
117
|
+
export class AppServiceProvider extends ServiceProvider {
|
|
118
|
+
register(): void {
|
|
119
|
+
this.app.singleton("MyApiClient", () => {
|
|
120
|
+
return new MyApiClient(env("API_URL") ?? "");
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async boot(): Promise<void> {
|
|
125
|
+
// use other bindings, gates, events, …
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Elsewhere:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
const client = app.resolve<MyApiClient>("MyApiClient");
|
|
134
|
+
```
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# Service providers
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
Service providers are the main way to bootstrap features in JCC Express MVC. Each provider is a class that receives the `Application` (the service container) and uses `register()` to bind services and `boot()` to run logic after all providers have registered—mirroring Laravel’s register / boot split.
|
|
6
|
+
|
|
7
|
+
Listing providers in `bootstrap/providers.ts` and implementing them under `app/Providers/` keeps startup explicit and ordered.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## What a provider does
|
|
12
|
+
|
|
13
|
+
A provider connects the framework to your app:
|
|
14
|
+
|
|
15
|
+
- Register container bindings (`bind`, `singleton`, `instance`) so other code can `resolve` them.
|
|
16
|
+
- Boot behavior that depends on those bindings (routes are not registered here—`RouteServiceProvider` loads route files during `app.run()`, but your `boot()` can use `Gate`, events, or other services already on the container).
|
|
17
|
+
|
|
18
|
+
The abstract base class lives in `jcc-express-mvc` (`ServiceProvider`). Extend it from `jcc-express-mvc/Core/Provider` (re-export) or the underlying `lib/Providers/ServiceProvider`.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## The `ServiceProvider` base class
|
|
23
|
+
|
|
24
|
+
- `constructor(protected app: Application)` — Stores the application instance as `this.app`.
|
|
25
|
+
- `abstract register(): void` — You must implement this. Put container registrations here only; avoid resolving services that other providers have not registered yet.
|
|
26
|
+
- `boot(): void | Promise<void>` — Optional override. Runs after every provider’s `register()` has completed (see When `register` and `boot` run). Use for `Gate.define`, subscribing to events, or resolving bindings that need the full container.
|
|
27
|
+
- `listen` / `subscribe` — Optional maps used when `bootListeners` or `bootSubscribers` is set on the provider so the `Application` wires event listeners or subscribers during registration.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Registration order (`ApplicationBuilder`)
|
|
32
|
+
|
|
33
|
+
In `withProviders()`, the builder does not use your array alone. It builds this chain:
|
|
34
|
+
|
|
35
|
+
1. `DatabaseServiceProvider` — Always first (database-related setup).
|
|
36
|
+
2. Your providers from `bootstrap/providers.ts` — If `AuthServiceProvider` (framework) is in the list but not first, the builder moves it to the front of your list so auth is set up early.
|
|
37
|
+
3. `QueueServiceProvider` — Always last in the chain.
|
|
38
|
+
|
|
39
|
+
That order matters when one provider’s `register()` assumes another binding already exists.
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## When `register` and `boot` run
|
|
44
|
+
|
|
45
|
+
During `bootstrap/app` build:
|
|
46
|
+
|
|
47
|
+
- For each provider class in the chain, `new Provider(app)` is called, then `register()` runs immediately.
|
|
48
|
+
- If the provider sets `bootSubscribers` / `bootListeners`, the app may call `subscribers()` / `listeners()` right after that provider registers.
|
|
49
|
+
|
|
50
|
+
`boot()` for all providers:
|
|
51
|
+
|
|
52
|
+
- When `app.boot()` runs (for example from `app.run()` if the app was not yet booted), the application loops `this.providers` and calls `boot()` on each in registration order.
|
|
53
|
+
|
|
54
|
+
Late registration:
|
|
55
|
+
|
|
56
|
+
- If a provider is registered after the application is already `booted`, `register()` still runs, and `bootProvider` is invoked immediately for that provider so it does not miss the boot phase.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Writing a provider
|
|
61
|
+
|
|
62
|
+
1. Create a class under `app/Providers/` extending `ServiceProvider`.
|
|
63
|
+
2. Implement `register()` with `this.app.bind`, `singleton`, etc.
|
|
64
|
+
3. Override `boot()` when you need work after all `register()` methods have finished.
|
|
65
|
+
4. Add the class to the `providers` array exported from `bootstrap/providers.ts` (already imported by `bootstrap/app.ts` via `withProviders`).
|
|
66
|
+
|
|
67
|
+
Keep `register()` free of heavy side effects and of `resolve()` calls that depend on providers later in the chain unless you know the order.
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Example
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// app/Providers/AppServiceProvider.ts
|
|
75
|
+
import { ServiceProvider } from "jcc-express-mvc/Core/Provider";
|
|
76
|
+
|
|
77
|
+
export class AppServiceProvider extends ServiceProvider {
|
|
78
|
+
register(): void {
|
|
79
|
+
this.app.singleton("Billing", () => new BillingService());
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async boot(): Promise<void> {
|
|
83
|
+
const billing = this.app.resolve<BillingService>("Billing");
|
|
84
|
+
// configure billing from config, register policies, etc.
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```typescript
|
|
90
|
+
// bootstrap/providers.ts
|
|
91
|
+
import { AppServiceProvider } from "../app/Providers/AppServiceProvider";
|
|
92
|
+
|
|
93
|
+
export const providers = [AppServiceProvider];
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## Framework providers you should know about
|
|
99
|
+
|
|
100
|
+
- `DatabaseServiceProvider` — Database / ORM wiring; runs before your list.
|
|
101
|
+
- `AuthServiceProvider` (when included) — Intended to run early among your providers.
|
|
102
|
+
- `QueueServiceProvider` — Queue binding; runs after your list.
|
|
103
|
+
- `RouteServiceProvider` — Not in your `bootstrap/providers.ts` list by default; it is registered as a singleton on `Application` and `loadRoutes()` is invoked from `app.run()` to load `route/web`, `route/api`, etc.
|
|
104
|
+
|
|
105
|
+
Your `AppServiceProvider` (and any custom providers) are where application-specific bindings and `boot` logic belong.
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
# Database: Getting Started
|
|
2
|
+
|
|
3
|
+
## Introduction
|
|
4
|
+
|
|
5
|
+
Almost every real application needs a database. JCC Express MVC keeps this flexible by supporting:
|
|
6
|
+
|
|
7
|
+
- **JCC ORM (default)** for SQL with a Knex-backed query layer
|
|
8
|
+
- **Sequelize** as an alternative SQL ORM
|
|
9
|
+
- **Mongoose** for MongoDB document workflows
|
|
10
|
+
|
|
11
|
+
In practice, most projects start with the default JCC stack, then switch ORM mode only when needed.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Supported database engines
|
|
16
|
+
|
|
17
|
+
With the default JCC SQL path (`DB_ORM=jcc`), connection clients include:
|
|
18
|
+
|
|
19
|
+
- `mysql2`
|
|
20
|
+
- `postgres`
|
|
21
|
+
- `sqlite`
|
|
22
|
+
- `better-sqlite3`
|
|
23
|
+
|
|
24
|
+
For document databases, use `DB_ORM=mongoose` with your Mongo connection settings.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Configuration
|
|
29
|
+
|
|
30
|
+
Database settings live in your app config and environment values:
|
|
31
|
+
|
|
32
|
+
- `app/Config/database.ts`
|
|
33
|
+
- `app/Config/index.ts` (exports `database`)
|
|
34
|
+
- `.env` values such as `DB_ORM`, `DB_CONNECTION`, `DB_HOST`, `DB_PORT`, `DB_DATABASE`, `DB_USERNAME`, `DB_PASSWORD`
|
|
35
|
+
|
|
36
|
+
Minimal shape from `app/Config/database.ts`:
|
|
37
|
+
|
|
38
|
+
```typescript
|
|
39
|
+
export const database = {
|
|
40
|
+
orm: config.get("DB_ORM", "jcc"),
|
|
41
|
+
sequelize: { /* ... */ },
|
|
42
|
+
mongoose: { /* ... */ },
|
|
43
|
+
};
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## SQLite configuration
|
|
49
|
+
|
|
50
|
+
For SQLite style connections (`sqlite` or `better-sqlite3`), the default JCC database layer resolves to a file database (`db.sqlite`) at project root.
|
|
51
|
+
|
|
52
|
+
Typical env setup:
|
|
53
|
+
|
|
54
|
+
```env
|
|
55
|
+
DB_ORM=jcc
|
|
56
|
+
DB_CONNECTION=sqlite
|
|
57
|
+
DB_DATABASE=db.sqlite
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
If you need a different file location, align your adapter/config with your deployment path strategy.
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## Choosing ORM mode
|
|
65
|
+
|
|
66
|
+
The provider layer uses your env/config to decide connection behavior:
|
|
67
|
+
|
|
68
|
+
- `DB_ORM=jcc` -> JCC/Knex SQL flow
|
|
69
|
+
- `DB_ORM=sequelize` -> Sequelize connection path
|
|
70
|
+
- `DB_ORM=mongoose` -> Mongoose connection path
|
|
71
|
+
|
|
72
|
+
Keep `DB_ORM` and `DB_CONNECTION` consistent (for example, do not pair `DB_ORM=mongoose` with SQL-only assumptions in your app code).
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Running queries
|
|
77
|
+
|
|
78
|
+
### Query builder style
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
const users = await DB.table("users").get();
|
|
82
|
+
const user = await DB.table("users").where("id", 1).first();
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### Raw SQL
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
const rows = await DB.runQuery("select * from users where active = ?", [1]);
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Or:
|
|
92
|
+
|
|
93
|
+
```typescript
|
|
94
|
+
const raw = DB.raw("select count(*) as total from users");
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Use bound parameters (`?`) instead of string interpolation to reduce SQL injection risk.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## More method usage examples
|
|
102
|
+
|
|
103
|
+
### Inserts
|
|
104
|
+
|
|
105
|
+
```typescript
|
|
106
|
+
await DB.table("users").insert({
|
|
107
|
+
name: "Abdou",
|
|
108
|
+
email: "abdou@example.com",
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Updates
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
await DB.table("users").where("id", 1).update({
|
|
116
|
+
name: "Abdou J",
|
|
117
|
+
});
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
### Deletes
|
|
121
|
+
|
|
122
|
+
```typescript
|
|
123
|
+
await DB.table("users").where("id", 1).delete();
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Filtering and ordering
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
const activeUsers = await DB.table("users")
|
|
130
|
+
.where("active", true)
|
|
131
|
+
.orderBy("created_at", "desc")
|
|
132
|
+
.limit(20)
|
|
133
|
+
.get();
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### Column selection
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
const slim = await DB.table("users")
|
|
140
|
+
.select("id", "name", "email")
|
|
141
|
+
.where("active", true)
|
|
142
|
+
.get();
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Counting and aggregates
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
const totalUsers = await DB.table("users").count("id as total");
|
|
149
|
+
const maxScore = await DB.table("users").max("score as max_score");
|
|
150
|
+
const avgScore = await DB.table("users").avg("score as avg_score");
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Pagination (offset/limit style)
|
|
154
|
+
|
|
155
|
+
```typescript
|
|
156
|
+
const page = 2;
|
|
157
|
+
const perPage = 15;
|
|
158
|
+
|
|
159
|
+
const rows = await DB.table("users")
|
|
160
|
+
.offset((page - 1) * perPage)
|
|
161
|
+
.limit(perPage)
|
|
162
|
+
.get();
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### Conditional create/update patterns
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
const exists = await DB.table("users").where("email", email).exists();
|
|
169
|
+
|
|
170
|
+
if (!exists) {
|
|
171
|
+
await DB.table("users").insert({ email, name });
|
|
172
|
+
} else {
|
|
173
|
+
await DB.table("users").where("email", email).update({ name });
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
### Raw expression inside builder
|
|
178
|
+
|
|
179
|
+
```typescript
|
|
180
|
+
const report = await DB.table("orders")
|
|
181
|
+
.select("status")
|
|
182
|
+
.select(DB.raw("count(*) as total"))
|
|
183
|
+
.groupBy("status")
|
|
184
|
+
.get();
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Transactions
|
|
190
|
+
|
|
191
|
+
JCC DB supports transaction wrappers:
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
await DB.transaction(async () => {
|
|
195
|
+
await DB.table("users").insert({ name: "Abdou" });
|
|
196
|
+
await DB.table("profiles").insert({ user_id: 1 });
|
|
197
|
+
});
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Keep schema-altering or implicit-commit statements out of transactional write flows unless you fully understand your database engine behavior.
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Migrations and seeders
|
|
205
|
+
|
|
206
|
+
Use ArtisanNode for schema and seed lifecycle:
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
bun artisanNode make:migration create_users_table
|
|
210
|
+
bun artisanNode migrate
|
|
211
|
+
bun artisanNode migrate:rollback
|
|
212
|
+
bun artisanNode migrate:reset
|
|
213
|
+
bun artisanNode migrate:fresh
|
|
214
|
+
bun artisanNode db:seed
|
|
215
|
+
bun artisanNode db:wipe
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Queue tables:
|
|
219
|
+
|
|
220
|
+
```bash
|
|
221
|
+
bun artisanNode make:queue-table
|
|
222
|
+
bun artisanNode migrate
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
---
|
|
226
|
+
|
|
227
|
+
## Practical recommendations
|
|
228
|
+
|
|
229
|
+
- Start with the default JCC stack unless a project requirement mandates Sequelize or Mongoose.
|
|
230
|
+
- Keep migrations in version control and run them in CI/CD.
|
|
231
|
+
- Prefer parameterized queries over manual string building.
|
|
232
|
+
- Validate production env values (`DB_ORM`, credentials, host/port) early in deployment.
|
|
233
|
+
|
|
234
|
+
---
|
|
235
|
+
|
|
236
|
+
## Summary
|
|
237
|
+
|
|
238
|
+
- JCC gives you one database entry point with multiple ORM backends.
|
|
239
|
+
- Config is env-driven and centered in `app/Config/database.ts`.
|
|
240
|
+
- Use `DB.table(...)`, `DB.runQuery(...)`, and migrations/seeders for day-to-day database work.
|