JCC Express

The Basics

Request

Introduction

Every HTTP handler receives an Express Request object. JCC Express MVC extends it as AppRequest and passes the same instance as req in route closures and in { req, res, next } / HttpContext patterns. After the framework’s request middleware runs, Laravel-style helpers are bound onto req, so you call req.input(), req.header(), and so on.

You can also use the global request() helper, which resolves the current AppRequest from the container (only valid during an HTTP request, after the per-request binding middleware runs).

Types: import Request from jcc-express-mvc as the AppRequest alias when you need to annotate parameters (for example FormRequest subclasses). For res, response(), Inertia, flash, and redirectBack, see Response.md in this folder.


When helpers are available

Helpers are attached after the framework request middleware runs (see Request lifecycle). Anything that runs before that middleware only sees a plain Express request.


Input from query, body, and merged data

These methods merge query and body for reads (input); file fields are handled separately via file / hasFile / store.

  • req.input() — All merged input, or req.input(key, default?) for one key.
  • req.all() — Merged query and body as a plain object ({ ...query, ...body }).
  • req.has(key) — Whether the key exists in merged input.
  • req.filled(key) — Present and non-empty string.
  • req.only(...keys) — Subset of keys.
  • req.except(...keys) — Copy of input without those keys (mutates the merged object for the copy path in the implementation—avoid relying on retaining the full original reference).
  • req.merge(data) — Shallow assign onto req.body.
  • req.replace(data) — Replace req.body entirely.

Route parameters remain on req.params (see Routing). input() does not include params; use req.params.id or route model binding in controllers.


Headers, content negotiation, and API-ish detection

  • req.header(name) — Case-insensitive header lookup.
  • req.ajax()X-Requested-With: XMLHttpRequest.
  • req.isJson() — JSON Content-Type.
  • req.wantsJson(), req.acceptsJson()Accept hints.
  • req.expectsJson() — Combines API path prefix (/api), AJAX, and JSON signals.
  • req.isInertia() — Presence of X-Inertia.
  • req.isApi()true when the path starts with /api.

Use these to branch between JSON errors and HTML redirects, or to tailor Inertia responses.


Auth, session, and flash

  • req.user — Set by auth middleware when a user is authenticated (shape depends on your auth setup).
  • req.session — Express session (requires session middleware).
  • req.flash(...) — Flash API from connect-flash (same session stack).
  • req.jccSession — Framework session bridge when available.
  • Global session() returns req.jccSession when you use the helper style.

Details: put / get / save / regenerate, flash alignment with res.with, and configuration notes are in Session.md.


Validation

  • req.validate(rules, customMessages?) — Validates against the current request (see Validation).
  • req.validated() — Retrieves validated data after validation has run (when your pipeline stores it).

The global validate(...) helper resolves the current request and calls validate on it.


Files and cookies

Upload handling relies on express-fileupload (registered in the framework middleware stack). Uploaded fields are available on req.files.

  • req.hasFile(key) — Whether an uploaded file field is present.
  • req.cookie(key) — Parsed cookies (requires cookie-parser).

Storing uploads to disk (file + store)

Use a fluent chain: call req.file(fieldName) to select the field, then req.store(directory) on the same req to persist it.

TypeScript
// single file field "avatar"
const fileName = req.file("avatar").store("uploads/avatars");
// → "my-photo-a1b2c3d4e5.jpg" (generated filename)

// multiple files under the same field name
const fileNames = req.file("images").store("uploads/gallery");
// → ["photo-abc123.jpg", "photo-def456.jpg"]
MethodReturnsDescription
req.file(fileName)AppRequest (chainable)Remembers the field name for the next store() call.
req.store(directory)string | string[]Saves the file(s) and returns the generated filename(s). Clears the remembered field after storing.

Behavior matches the framework’s saveImage helper for local disk storage.

  • Files are written under public/<directory>/ (created if missing).
  • Each filename is slugged from the original name plus a random suffix; the original extension (or mimetype-derived extension) is preserved.
  • When the field contains multiple files (array), every file is saved and an array of filenames is returned.

store() is synchronous and returns immediately with the generated name(s); the underlying mv call runs asynchronously—handle errors in your route or wrap in try/catch if you need strict failure handling.

Uploading to Cloudinary (storeToCloudinary)

Upload a field directly to Cloudinary without saving to disk first. First run bun artisanNode publish Cloudinary to create app/Config/cloudinary.ts and register CloudinaryServiceProvider. Then set Cloudinary credentials in .env (CLOUDINARY_NAME, CLOUDINARY_API_KEY, CLOUDINARY_SECRET). See Cloudinary for setup details.

TypeScript
// return the public URL (default)
const url = await req.storeToCloudinary("image");
// → "https://res.cloudinary.com/.../image/upload/v123/abc.jpg"

// multiple files → array of URLs
const urls = await req.storeToCloudinary("images");

// pass Cloudinary upload options (folder, transformation, etc.)
const url = await req.storeToCloudinary("image", { folder: "avatars" });

// return the full Cloudinary upload response instead of just the URL
const result = await req.storeToCloudinary("image", {}, false);
// → { url, public_id, secure_url, ... }
ParameterTypeDefaultDescription
fileNamestringThe req.files field name to upload.
optionRecord<string, any>{}Options forwarded to cloudinary.uploader.upload (e.g. folder, resource_type).
returnUrlbooleantrueWhen true, returns res.url (or an array of URLs). When false, returns the full upload response object(s).

When the field contains multiple files, storeToCloudinary uploads each one and returns an array (URLs or full responses, depending on returnUrl).

FormRequest equivalents

FormRequest subclasses expose the same upload API:

TypeScript
// disk storage
const name = userRequest.file("avatar").store("uploads/avatars");

// Cloudinary
const url = await userRequest.storeToCloudinary("avatar", { folder: "users" });

Other helpers

  • req.bearerToken() — Parses Authorization: Bearer ….
  • req.userAgent()User-Agent header.
  • req.isMethod("post") — Case-insensitive HTTP method check.
  • req.fullUrl() — Protocol, host, and originalUrl.
  • req.csrfToken() — Available when csrf() middleware ran; same value as res.locals._token (see CSRF-protection).

Express-native fields such as req.path, req.query, req.body, req.params, req.ip, and req.get("host") remain available.


FormRequest (action injection)

For validated or authorized form flows, extend FormRequest (jcc-express-mvc/Core/FormRequest) and type-hint the subclass on a controller method decorated with @Method(). The container constructs it with the current AppRequest, runs await rules() (your async or sync rules hook), then calls the action.

Typical pattern:

  • Implement protected async rules() (or sync) and call this.validate({ field: ["required"], … }) with rule arrays.
  • Optionally override protected authorize() — the base implementation returns false. If your override returns true, the constructor throws AuthorizationException (deny). Return false to allow the request to continue.

Expose domain actions such as save() on your subclass (UserRequest, etc.) and call them from the controller after injection.


Summary

  • AppRequest = Express Request + framework helpers bound per request.
  • Access via req, request(), or HttpContext (parameter defaults only for context objects—see Routing).
  • Prefer req.input / only / validate for Laravel-like ergonomics; keep req.params for route segments and binding.
  • File uploads: req.file(name).store(directory) for local disk (public/<directory>/), or await req.storeToCloudinary(name, options?, returnUrl?) for Cloudinary.
  • Sending the response: Response.md (AppResponse, res.inertia, res.with, redirectBack, globals view / redirect / back / inertia).
  • Session and flash: Session.md.