UNPKG

jcc-express-starter

Version:
1,686 lines (1,213 loc) • 159 kB
# JCC Express MVC Framework Documentation Welcome to the JCC Express MVC framework documentation. JCC Express MVC is a powerful, expressive web application framework built on Express.js, designed to make web development a creative and enjoyable experience. ## šŸ“š Documentation For complete documentation, visit: **[https://www.jcc-express.uk/](https://www.jcc-express.uk/)** --- # Introduction JCC Express MVC is a modern, full-featured web application framework built on Express.js that follows Laravel-style architecture patterns. It is **specifically designed and optimized for the Bun runtime**, offering a familiar developer experience for those coming from the Laravel ecosystem while leveraging the exceptional performance of Bun. The framework provides a robust set of tools and features including routing, controllers, middleware, database abstraction with Eloquent ORM, authentication, caching, queues, events, and much more. Whether you're building a simple API or a complex enterprise application, JCC Express MVC has the tools you need. ## Why JCC Express MVC? - **Laravel-Inspired**: Familiar patterns and conventions for Laravel developers - **Bun Optimized**: Built specifically for Bun runtime for maximum performance - **Type-Safe**: Full TypeScript support with excellent type inference - **Eloquent ORM**: Beautiful, expressive database interactions - **Modern Architecture**: Service container, dependency injection, and more - **Developer Experience**: Intuitive APIs and comprehensive tooling ## Requirements Before you begin, ensure your machine meets the following requirements: - **Bun runtime** (v1.0.0 or higher) - Required - **Node.js** (v18.0.0 or higher) - For npm package management - **Database**: MySQL 5.7+, PostgreSQL 10+, or SQLite 3.8+ - **Redis** (optional) - For caching and queue management --- # Installation ## Prerequisites Before using this framework, you must have Bun installed on your machine. 1. Install Bun (if not already installed): ```bash curl -fsSL https://bun.sh/install | bash ``` 2. Verify Bun installation: ```bash bun --version ``` ## Creating a New Project To create a new JCC Express MVC project, use the official starter template: ```bash bunx jcc-express-starter my-express-app ``` This command will create a new directory named `my-express-app` with all the necessary files and directory structure for your application. ### Project Setup After creating your project, navigate to the project directory: ```bash cd my-express-app ``` Then install the project dependencies: ```bash npm install ``` ### Starting the Development Server Once dependencies are installed, you can start the development server: ```bash bun run dev ``` Your application will be available at `http://localhost:5500` (or the port specified in your `.env` file). --- # Configuration JCC Express MVC uses a centralized configuration system that allows you to manage your application's settings in a clean, organized manner. All configuration files are stored in the `app/Config` directory, and sensitive values are managed through environment variables. ## Environment Configuration For security and flexibility, JCC Express MVC uses environment files to store sensitive configuration values. Your application's `.env` file should not be committed to version control, as it may contain API keys, passwords, and other sensitive information. ### Environment File Setup Copy the example environment file to create your own: ```bash cp .env.example .env ``` ### Environment Variables Edit your `.env` file to configure your application. Here are the essential variables: ```env # Application APP_NAME="JCC Express" APP_ENV=local APP_KEY=your-secret-key-here APP_DEBUG=true APP_URL=http://localhost:5500 PORT=5500 # Database DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=jcc_express DB_USERNAME=root DB_PASSWORD= # Cache CACHE_DRIVER=memory CACHE_PREFIX=jcc_express_cache # Session SESSION_DRIVER=memory SESSION_SECRET=your-session-secret # Redis (optional) REDIS_HOST=127.0.0.1 REDIS_PASSWORD=null REDIS_PORT=6379 ``` ### Accessing Environment Variables You can access environment variables in your application using the `config` helper or by importing the config directly: ```typescript import { config } from "@/app/Config"; const appName = config.get("APP_NAME"); const dbConnection = config.get("DB_CONNECTION"); ``` ## Configuration Files All configuration files are located in the `app/Config/` directory. These files allow you to configure specific aspects of your application. ### CORS Configuration The `cors.ts` file allows you to configure Cross-Origin Resource Sharing settings: ```typescript // app/Config/cors.ts export const cors = { origin: "*", // or specify allowed origins: ["http://localhost:3000"] methods: "GET,HEAD,PUT,PATCH,POST,DELETE", preflightContinue: false, optionsSuccessStatus: 204, credentials: true, // Allow cookies }; ``` ### Rate Limiting Configure rate limiting in `app/Config/rate-limit.ts`: ```typescript export const rateLimit = { windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // Limit each IP to 100 requests per windowMs }; ``` ### Session Configuration Configure session settings in `app/Config/session.ts`: ```typescript export const session = { secret: process.env.SESSION_SECRET || "your-secret-key", resave: false, saveUninitialized: false, cookie: { secure: process.env.APP_ENV === "production", httpOnly: true, maxAge: 24 * 60 * 60 * 1000, // 24 hours }, }; ``` --- # Global Helpers JCC Express MVC provides several global helper functions that are available everywhere in your application without needing to import them. These helpers are automatically registered when your application boots. ## Available Global Helpers ### Application Instance Access the application instance anywhere in your code: ```typescript const userService = app.resolve("UserService"); const config = app.resolve("Config"); ``` ### Environment Variables Get environment variable values with optional default: ```typescript const port = env("PORT", "5500"); const dbHost = env("DB_HOST"); const appName = env("APP_NAME", "JCC Express"); ``` ### Event Dispatching Dispatch events using the `emit` helper: ```typescript import { UserRegistered } from "@/Events/UserRegistered"; // Dispatch an event await emit(new UserRegistered(user)); ``` ### Queue Job Dispatching Dispatch queue jobs using the `dispatch` helper: ```typescript import { ProcessPodcast } from "@/Jobs/ProcessPodcast"; // Dispatch a job immediately await dispatch(new ProcessPodcast(podcast)); // Jobs with delay are automatically handled const job = new ProcessPodcast(podcast); job.delay = 5000; // 5 seconds await dispatch(job); ``` ### String Utilities Access the `Str` utility class: ```typescript const slug = str().slug("Hello World"); // "hello-world" const random = str().random(10); // Random string const camel = str().camel("hello_world"); // "helloWorld" ``` ### Password Hashing Hash and verify passwords: ```typescript // Hash a password const hashedPassword = await bcrypt("plain-text-password"); // Verify a password const isValid = await verifyHash("plain-text-password", hashedPassword); ``` ### JWT Tokens Sign and verify JWT tokens: ```typescript // Sign a JWT token const token = jwtSign({ id: 1, email: "user@example.com" }); // Verify a JWT token try { const payload = jwtVerify(token); console.log(payload); // { id: 1, email: "user@example.com" } } catch (error) { // Token is invalid } ``` ### Root Path Get the root path of your application: ```typescript const storagePath = rootPath("storage/app"); const configPath = rootPath("app/Config"); const publicPath = rootPath("public"); ``` ## Complete Example Here's an example showing multiple global helpers in use: ```typescript import { UserRegistered } from "@/Events/UserRegistered"; import { SendWelcomeEmail } from "@/Jobs/SendWelcomeEmail"; // In a controller or service async function registerUser(data: any) { // Hash password const hashedPassword = await bcrypt(data.password); // Create user const user = await User.create({ ...data, password: hashedPassword, }); // Dispatch event await emit(new UserRegistered(user)); // Dispatch job await dispatch(new SendWelcomeEmail(user)); // Generate JWT token const token = jwtSign({ id: user.id, email: user.email }); // Get storage path const avatarPath = rootPath(`storage/app/avatars/${user.id}`); return { user, token }; } ``` --- # Directory Structure JCC Express MVC follows a convention-over-configuration approach with a familiar, organized directory structure. Understanding this structure will help you know where to place files and how the framework organizes code. ## Root Directory Structure ``` project-root/ ā”œā”€ā”€ app/ # Application core │ ā”œā”€ā”€ Config/ # Configuration files │ ā”œā”€ā”€ Events/ # Event classes │ ā”œā”€ā”€ Http/ # HTTP layer │ │ ā”œā”€ā”€ Controllers/ # Controller classes │ │ ā”œā”€ā”€ Middlewares/ # Middleware classes │ │ ā”œā”€ā”€ Requests/ # Form request classes │ │ └── kernel.ts # HTTP kernel (middleware registration) │ ā”œā”€ā”€ Jobs/ # Queue job classes │ ā”œā”€ā”€ Listener/ # Event listener classes │ ā”œā”€ā”€ Models/ # Eloquent model classes │ ā”œā”€ā”€ Observer/ # Model observer classes │ ā”œā”€ā”€ Providers/ # Service provider classes │ ā”œā”€ā”€ Repository/ # Repository classes │ └── Services/ # Service classes ā”œā”€ā”€ bootstrap/ # Application bootstrapping │ ā”œā”€ā”€ app.ts # Application initialization │ └── providers.ts # Service provider registration ā”œā”€ā”€ database/ # Database files │ ā”œā”€ā”€ migrations/ # Database migration files │ └── seeders/ # Database seeder classes ā”œā”€ā”€ public/ # Publicly accessible files │ └── build/ # Compiled assets ā”œā”€ā”€ resources/ # Raw, uncompiled assets │ ā”œā”€ā”€ views/ # Template files (jsBlade) │ ā”œā”€ā”€ css/ # CSS files │ └── js/ # JavaScript files ā”œā”€ā”€ routes/ # Route definitions │ ā”œā”€ā”€ web.ts # Web routes │ └── api.ts # API routes ā”œā”€ā”€ storage/ # Storage directory │ ā”œā”€ā”€ app/ # Application files │ └── sessions/ # Session files ā”œā”€ā”€ tests/ # Test files │ ā”œā”€ā”€ Feature/ # Feature tests │ └── Unit/ # Unit tests └── server.ts # Application entry point ``` ## The App Directory The `app` directory contains the core code of your application. Almost all of your application's classes will be in this directory. ### Controllers Controllers are stored in `app/Http/Controllers` and handle incoming HTTP requests. They contain the logic for processing requests and returning responses. ### Middleware Middleware classes are stored in `app/Http/Middlewares` and provide a mechanism for filtering HTTP requests entering your application. ### Models Eloquent models are stored in `app/Models`. Models allow you to query for data in your database tables and insert new records. ### Providers Service providers are stored in `app/Providers` and are the central place to bootstrap your application. They bind services into the service container and register event listeners. ## The Routes Directory The `routes` directory contains all of your route definitions. The framework ships with two route files: `web.ts` for your web routes and `api.ts` for your API routes. ## The Resources Directory The `resources` directory contains your raw, uncompiled assets such as CSS, JavaScript, and view templates. ## The Public Directory The `public` directory contains the `index.php` file, which is the entry point for all requests entering your application. This directory also serves as a good place to store assets such as images, fonts, and compiled CSS and JavaScript files. --- # Lifecycle Overview Understanding the request lifecycle of JCC Express MVC will help you better understand how the framework works and where to hook into the framework's execution flow. JCC Express MVC uses Express's native request/response lifecycle, extending Express's `Request` and `Response` objects with additional methods through `AppRequest` and `AppResponse` interfaces. This means all standard Express middleware and methods work seamlessly with the framework. ## Request Lifecycle Every request to your JCC Express MVC application follows a specific lifecycle path. Understanding this lifecycle is crucial for building effective applications. The framework uses Express's native HTTP handling, so requests and responses are standard Express objects with additional framework methods. ### Entry Point The entry point for all requests to a JCC Express MVC application is the `server.ts` file. This file is very simple and serves as the starting point for loading the rest of your application: ```typescript // server.ts import { app } from "./bootstrap/app"; app.run(); ``` ### Application Bootstrap When a request enters your application, it first goes through the application bootstrap process: 1. **Service Container**: The application instance is created and the service container is initialized 2. **Service Providers**: All service providers are registered and booted 3. **Configuration**: Application configuration is loaded 4. **Routes**: Route files are loaded and registered ### HTTP Kernel After bootstrapping, the request is sent to the HTTP kernel (`app/Http/kernel.ts`). The kernel handles: 1. **Global Middleware**: Applies middleware that should run on every request 2. **Route Matching**: Matches the request URI to a defined route 3. **Route Middleware**: Applies middleware specific to the matched route ### Route Execution Once a route is matched: 1. **Controller Resolution**: If the route uses a controller, the controller is instantiated via the service container 2. **Dependency Injection**: Constructor and method dependencies are automatically resolved 3. **Method Execution**: The controller method or route closure is executed 4. **Response Generation**: A response is generated and returned ### Response The response flows back through the middleware stack (in reverse order) and is finally sent to the client. ## Service Provider Lifecycle Service providers are the central place where your application is bootstrapped. They have two lifecycle methods: 1. **Register**: Bind services into the service container 2. **Boot**: Perform any actions after all providers are registered Understanding this lifecycle will help you know where to place your code and how to extend the framework's functionality. --- # Service Container The JCC Express MVC service container is a powerful tool for managing class dependencies and performing dependency injection. Dependency injection is a method of removing hard-coded class dependencies and replacing them with injected dependencies, making your code more maintainable and testable. ## Understanding the Service Container The service container is the central place where all of your application's services are registered and resolved. It's responsible for automatically resolving class dependencies through constructor injection. ## Dependency Injection The service container automatically resolves dependencies by examining a class's constructor type hints. When the container sees a type-hinted dependency, it will attempt to resolve it from the container. ### Basic Dependency Injection You can type-hint dependencies in your controllers and other classes. The service container will automatically resolve them: ```typescript import { Inject } from "jcc-express-mvc"; import { UserService } from "@/Services/UserService"; @Inject() export class UserController { constructor(private userService: UserService) {} async index() { return this.userService.all(); } } ``` ### Binding Services You can bind services into the container in your service providers. It's recommended to use `class.name` instead of string literals for better maintainability: ```typescript import { ServiceProvider } from "jcc-express-mvc"; import { UserService } from "@/Services/UserService"; import { EmailService } from "@/Services/EmailService"; export class AppServiceProvider extends ServiceProvider { register(): void { // Bind as singleton using class.name (recommended) this.app.singleton(UserService.name, () => { return new UserService(); }); // Bind as instance (new instance each time) using class.name this.app.bind(EmailService.name, () => { return new EmailService(); }); // You can also bind directly without a factory function this.app.singleton(UserService.name, UserService); this.app.bind(EmailService.name, EmailService); } } ``` **Why use `class.name`?** - More maintainable: If you rename the class, TypeScript will catch errors - Less error-prone: No typos in string literals - Better IDE support: Autocomplete and refactoring work better - Type-safe: TypeScript can verify the class name matches ### Resolving Services You can resolve services from the container using either the class name or `class.name`: ```typescript import { app } from "@/bootstrap/app"; import { UserService } from "@/Services/UserService"; // Using class.name (recommended) const userService = app.resolve(UserService.name); // or with type annotation const userService = app.resolve<UserService>(UserService.name); // Using string literal (still works, but not recommended) const userService = app.resolve("UserService"); ``` ## When to Use the Service Container You typically don't need to manually interact with the service container. The framework automatically resolves dependencies through constructor injection. However, you may need to interact with the container when: - Writing service providers - Manually resolving services - Binding custom services --- # Service Providers Service providers are the central place where all JCC Express MVC application bootstrapping takes place. Your own application, as well as all of the framework's core services, are bootstrapped via service providers. ## What are Service Providers? Service providers are classes that bootstrap your application by binding services into the service container, registering event listeners, and performing other initialization tasks. Think of service providers as the "glue" that holds your application together. ## Writing Service Providers All service providers extend the `ServiceProvider` class. Most service providers contain a `register` method and a `boot` method. ### The Register Method Within the `register` method, you should only bind things into the service container. You should never attempt to register any event listeners, routes, or any other piece of functionality within the `register` method. ```typescript import { ServiceProvider } from "jcc-express-mvc"; import { UserService } from "@/Services/UserService"; import { EmailService } from "@/Services/EmailService"; export class AppServiceProvider extends ServiceProvider { /** * Register any application services. */ register(): void { // Bind services into the container using class.name (recommended) this.app.singleton(UserService.name, () => { return new UserService(); }); // Or bind directly this.app.bind(EmailService.name, EmailService); } } ``` ### The Boot Method The `boot` method is called after all other service providers have been registered, meaning you have access to all other services that have been registered by the framework. ```typescript import { ServiceProvider } from "jcc-express-mvc"; export class AppServiceProvider extends ServiceProvider { register(): void { // ... } /** * Bootstrap any application services. */ boot(): void { // Access other services here const userService = this.app.resolve("UserService"); // Perform initialization tasks } } ``` ## Registering Providers All service providers are registered in the `bootstrap/providers.ts` file: ```typescript import { AppServiceProvider } from "@/Providers/AppServiceProvider"; import { EventServiceProvider } from "@/Providers/EventServiceProvider"; import { QueueServiceProvider } from "@/Providers/QueueServiceProvider"; export const providers = [ AppServiceProvider, EventServiceProvider, QueueServiceProvider, // Add your custom providers here ]; ``` ## Event Service Providers For event-related functionality, extend the `EventServiceProvider` class: ```typescript import { EventServiceProvider as ServiceProvider } from "jcc-express-mvc"; import { UserRegistered } from "@/Events/UserRegistered"; import { SendWelcomeEmail } from "@/Listeners/SendWelcomeEmail"; export class EventServiceProvider extends ServiceProvider { protected listen: Record<any, Function[]> = { UserRegistered: [SendWelcomeEmail], }; protected subscribe: any[] = []; register(): void {} } ``` ## Deferred Providers If your provider only registers bindings in the service container, you may choose to defer its registration until one of its registered bindings is actually needed. Deferring the loading of such a provider will improve the performance of your application, since it is not loaded from the filesystem on every request. --- # Routing ## Basic Routing The most basic JCC Express routes accept a URI and a closure, providing a very simple and expressive method of defining routes and behavior without complicated routing configuration files: ```typescript import { Route } from "jcc-express-mvc/Core"; Route.get("/", (req, res) => { return res.json({ message: "Hello World" }); }); // Using Controller Array Syntax Route.get("/user", [UserController, "index"]); ``` ## Available Router Methods The router allows you to register routes that respond to any HTTP verb: ```typescript Route.get(uri, callback); Route.post(uri, callback); Route.put(uri, callback); Route.patch(uri, callback); Route.delete(uri, callback); ``` ## Route Parameters ### Required Parameters Sometimes you will need to capture segments of the URI within your route. For example, you may need to capture a user's ID from the URL. The framework supports both `:id` (Express-style) and `{id}` (Laravel-style) syntaxes: ```typescript // Using :id syntax (Express-style) Route.get("/user/:id", (req, res) => { return res.json({ id: req.params.id }); }); // Using {id} syntax (Laravel-style) - also works Route.get("/user/{id}", (req, res) => { return res.json({ id: req.params.id }); }); ``` Both syntaxes work identically and can be used interchangeably based on your preference. ### Route Model Binding JCC Express automatically resolves Eloquent models defined in routes or controller actions whose type-hinted variable names match a route segment name. ```typescript // Define a route with model binding (both :user and {user} work) Route.get("/users/:user", [UsersController, "show"]); // or Route.get("/users/{user}", [UsersController, "show"]); // Controller import { httpContext } from "jcc-express-mvc"; import { Inject, Method } from "jcc-express-mvc"; @Inject() class UsersController { @Method() async show(user: User, { res } = httpContext) { return res.json(user); } } ``` #### Binding by Specific Column You can specify which column to use for model binding by using the `{column$param}` syntax: ```typescript // Find user by slug instead of ID Route.get("/users/{slug$user}", [UsersController, "show"]); // or with Express-style syntax Route.get("/users/:slug$user", [UsersController, "show"]); // Controller - the model will be resolved using the 'slug' column @Inject() class UsersController { @Method() async show(user: User, { res } = httpContext) { return res.json(user); // User found by slug column } } ``` **Note:** The syntax `{column$param}` or `:column$param` tells the framework to find the model using the specified column instead of the default primary key. ## Route Groups Route groups allow you to share route attributes, such as middleware, across a large number of routes without needing to define those attributes on each individual route. ### Middleware To assign middleware to all routes within a group, you may use the `middleware` method before defining the group. Middleware are executed in the order they are listed in the array: ```typescript Route.middleware(["auth"]).group(() => { Route.get("/", (req, res) => { // Uses Auth Middleware }); Route.get("/user/profile", (req, res) => { // Uses Auth Middleware }); }); ``` ### Route Prefixes The `prefix` method may be used to prefix each route in the group with a given URI. For example, you may want to prefix all route URIs within the group with `admin`: ```typescript Route.prefix("admin").group(() => { Route.get("/users", (req, res) => { // Matches The "/admin/users" URL }); }); ``` ### Route Controllers If a group of routes all utilize the same controller, you may use the `controller` method to define the common controller for all of the routes within the group. Then, when defining the routes, you only need to provide the controller method that they invoke: ```typescript Route.controller(OrderController).group(() => { // Both :id and {id} syntaxes work Route.get("/orders/:id", "show"); // or Route.get("/orders/{id}", "show"); Route.post("/orders", "store"); }); ``` --- # Controllers ## Introduction Instead of defining all of your request handling logic as closures in your route files, you may wish to organize this behavior using "controller" classes. Controllers can group related request handling logic into a single class. ## Basic Controllers Controllers are stored in the `app/Http/Controllers` directory. ```typescript import { httpContext } from "jcc-express-mvc"; import { User } from "@/Models/User"; export class UserController { /** * Show the profile for a given user. */ async show({ req, res } = httpContext) { const user = await User.find(req.params.id); return res.json({ user }); } } ``` ## Dependency Injection & Method Injection The framework features an improved controller architecture with method injection and elegant context handling. ### Constructor Injection The JCC Express service container is used to resolve all controllers. As a result, you are able to type-hint any dependencies your controller may need in its constructor. ```typescript import { Inject } from "jcc-express-mvc"; @Inject() class UsersController { constructor(private readonly service: UserService) {} } ``` ### Method Injection In addition to constructor injection, you may also type-hint dependencies on your controller's methods. A common use-case for method injection is injecting the `User` model into your controller methods. ```typescript import { Inject, Method } from "jcc-express-mvc"; @Inject() class UsersController { @Method() async show(user: User, { res } = httpContext) { return res.json(user); } } ``` ### HTTP Context You can destructure the HTTP context (`req`, `res`, `next`) directly in your method signature: ```typescript @Method() async index({ req } = httpContext) { const { query } = req.query; // ... } ``` --- # Middleware ## Introduction Middleware provide a convenient mechanism for inspecting and filtering HTTP requests entering your application. For example, JCC Express includes a middleware that verifies the user of your application is authenticated. If the user is not authenticated, the middleware will redirect the user to the login screen. However, if the user is authenticated, the middleware will allow the request to proceed further into the application. ## Defining Middleware Middleware can be created inside the `app/Http/Middlewares` directory. Import `Request`, `Response`, and `Next` from `"jcc-express-mvc"`: ```typescript // app/Http/Middlewares/AuthMiddleware.ts import { Request, Response, Next } from "jcc-express-mvc"; export function AuthMiddleware(req: Request, res: Response, next: Next) { if (!req.user) { return res.status(401).json({ message: "Unauthorized" }); } next(); } ``` **Note:** Always import `Request`, `Response`, and `Next` from `"jcc-express-mvc"` - these are aliases for `AppRequest`, `AppResponse`, and `AppNext` that provide full type support. ## Registering Middleware ### Global Middleware If you want a middleware to run during every HTTP request to your application, list the middleware class in the `middleware` property of your `app/Http/kernel.ts` class. ### Assigning Middleware to Routes If you would like to assign middleware to specific routes, you should first assign the middleware a key in your `app/Http/kernel.ts` file. ```typescript // app/Http/kernel.ts export class Kernel { static middlewareAliases = { auth: AuthMiddleware, }; } ``` Once defined, you may use the `middleware` method to assign middleware to a route: ```typescript Route.middleware(["auth"]).get("/profile", (req, res) => { // ... }); ``` --- # Requests & Responses JCC Express MVC extends Express's native `Request` and `Response` objects with additional methods through `AppRequest` and `AppResponse` interfaces. This provides a clean, fluent interface for working with HTTP requests and responses while maintaining compatibility with all Express methods. ## HTTP Requests JCC Express MVC uses Express's `Request` object extended with additional methods via the `AppRequest` interface. All standard Express request methods are available, plus the framework's custom methods. ### Accessing The Request In controllers, you use `httpContext` to access the request and response objects. TypeScript automatically infers the correct types: ```typescript import { httpContext } from "jcc-express-mvc"; class UserController { async store({ req, res } = httpContext) { const name = req.body.name; const email = req.body.email; // ... } } ``` **Note:** In route closures (like `Route.get("/", async (req, res) => {})`), TypeScript automatically knows the types - you don't need to import or type them explicitly. ### Request Data Methods #### Basic Input Access ```typescript // Get all input (body + query) const all = req.body; // Get specific input value const name = req.input("name"); const email = req.input("email", "default@example.com"); // With default // Get query parameters (Express native) const page = req.query.page; // Get route parameters (Express native) const userId = req.params.id; ``` #### Input Helper Methods ```typescript // Check if input key exists if (req.has("name")) { // ... } // Check if input key exists and is not empty if (req.filled("email")) { // ... } // Get only specified keys const data = req.only("name", "email"); // Get all except specified keys const data = req.except("password", "password_confirmation"); // Merge new data into request req.merge({ status: "active" }); // Replace all request data req.replace({ name: "John", email: "john@example.com" }); ``` ### Authentication & User ```typescript // Access authenticated user const user = req.user; // Check if user is authenticated if (req.user) { // User is authenticated } ``` ### Flash Messages ```typescript // Get all flash messages const flash = req.flash(); // Get flash messages by type const successMessages = req.flash("success"); const errorMessages = req.flash("error"); // Set flash message req.flash("success", "User created successfully!"); req.flash("error", ["Error 1", "Error 2"]); // Multiple messages ``` ### Request Validation ```typescript // Validate request data await req.validate({ name: ["required", "string", "max:255"], email: ["required", "email", "unique:users,email"], password: ["required", "string", "min:8", "confirmed"], }); // Get validated data const validated = await req.validated(); ``` ### Request Detection Methods ```typescript // Check if request is an API request if (req.isApi()) { // Request is to /api/* route } // Check if request is AJAX if (req.ajax()) { // Request is XMLHttpRequest } // Check if request wants JSON if (req.wantsJson()) { // Accept header includes JSON } // Check if request body is JSON if (req.isJson()) { // Content-Type is application/json } // Check if request accepts JSON if (req.acceptsJson()) { // Accept header prioritizes JSON } // Check if request expects JSON response if (req.expectsJson()) { // Combines isApi, ajax, wantsJson, isJson checks } // Check if request is Inertia request if (req.isInertia()) { // Request has X-Inertia header } ``` ### Headers & Cookies ```typescript // Get header value const authHeader = req.header("Authorization"); const userAgent = req.userAgent(); // Get cookie value const token = req.cookie("auth_token"); // Get bearer token from Authorization header const token = req.bearerToken(); ``` ### File Uploads ```typescript // Check if file exists if (req.hasFile("avatar")) { // File was uploaded } // Get file and store it const filePath = req.file("avatar").store("avatars"); // Access file directly (Express native) const file = req.files?.avatar; ``` ### Request Information ```typescript // Get full URL const fullUrl = req.fullUrl(); // Check HTTP method if (req.isMethod("POST")) { // ... } // Get request ID const requestId = req.id; // Access JCC session const session = req.jccSession; // Get previous URLs const previousUrls = req.previsiousUrls; ``` ### Complete Request Example ```typescript // In route closures, TypeScript automatically infers types - no imports needed Route.post("/users", async (req, res) => { // Validate request await req.validate({ name: ["required", "string"], email: ["required", "email"], }); // Get validated data const data = await req.validated(); // Check request type if (req.expectsJson()) { // Return JSON response return res.json({ message: "User created", user: data }); } // Set flash message req.flash("success", "User created successfully!"); // Redirect return res.redirect("/users"); }); ``` **Note:** In route closures, you don't need to import `Request` or `Response` - TypeScript automatically knows the types. Only import them if you need to use them in middleware or other contexts. ## HTTP Responses JCC Express MVC uses Express's `Response` object extended with additional methods via the `AppResponse` interface. All standard Express response methods are available, plus the framework's custom methods. ### JSON Responses **Note:** In route closures, TypeScript automatically infers the types for `req` and `res` - you don't need to import or type them explicitly. ```typescript // JSON response (Express native) return res.json({ name: "Abigail", state: "CA", }); // JSON with status code return res.status(201).json({ message: "Created", data: user, }); ``` ### Redirect Responses ```typescript // Simple redirect (Express native) return res.redirect("/home"); // Redirect with status code return res.redirect(303, "/home"); // Redirect back to previous URL return res.redirectBack(); // Redirect with flash message return res.with("User created!", "success").redirect("/users"); ``` ### View Responses ```typescript // Render jsBlade view (Express native res.render) return res.render("welcome", { name: "John", title: "Welcome", }); ``` ### Inertia Responses ```typescript // Render Inertia page return res.inertia("Users/Index", { users: users, }); // Inertia redirect return res.inertiaRedirect("/users", "User created!", "success"); ``` ### File Downloads ```typescript // Download file (Express native) return res.download("/path/to/file.pdf"); // Download with custom filename return res.download("/path/to/file.pdf", "custom-name.pdf"); ``` ### Response Headers ```typescript // Set header (Express native) res.set("X-Custom-Header", "value"); // Set multiple headers res.set({ "X-Custom-Header": "value", "X-Another-Header": "another-value", }); // Get header const contentType = res.get("Content-Type"); ``` ### Status Codes ```typescript // Set status code (Express native) res.status(201); // Chain with response return res.status(201).json({ message: "Created" }); // Common status codes res.status(200); // OK res.status(201); // Created res.status(400); // Bad Request res.status(401); // Unauthorized res.status(404); // Not Found res.status(500); // Internal Server Error ``` ### Flash Messages with Redirects ```typescript // Set flash message and redirect return res.with("User created successfully!", "success").redirect("/users"); // Different flash types res.with("Error occurred!", "error").redirect("/users"); res.with("Warning message!", "warning").redirect("/users"); res.with("Info message!", "info").redirect("/users"); ``` ### Complete Response Example ```typescript // In route closures, TypeScript automatically infers types - no imports needed Route.post("/users", async (req, res) => { const user = await User.create(await req.validated()); if (req.expectsJson()) { return res.status(201).json({ message: "User created", user: user, }); } return res .with("User created successfully!", "success") .redirect("/users"); }); ``` ## Request & Response Lifecycle JCC Express MVC uses Express's native request/response lifecycle. The framework extends these objects with additional methods while maintaining full compatibility with Express middleware and methods. ### Standard Express Methods All standard Express `Request` and `Response` methods are available: **Request Methods:** - `req.body` - Request body - `req.query` - Query parameters - `req.params` - Route parameters - `req.headers` - Request headers - `req.cookies` - Request cookies - `req.get()` - Get header - `req.is()` - Check content type - And all other Express request methods **Response Methods:** - `res.json()` - JSON response - `res.send()` - Send response - `res.render()` - Render view - `res.redirect()` - Redirect - `res.status()` - Set status code - `res.set()` - Set header - `res.cookie()` - Set cookie - And all other Express response methods ### Framework Extensions The framework adds the following methods to enhance the Express objects: **AppRequest Extensions:** - `req.validate()` - Validate request data - `req.validated()` - Get validated data - `req.input()` - Get input value - `req.has()` - Check if input exists - `req.filled()` - Check if input is filled - `req.only()` - Get only specified keys - `req.except()` - Get all except specified keys - `req.merge()` - Merge new data - `req.replace()` - Replace request data - `req.flash()` - Flash messages - `req.isApi()` - Check if API request - `req.ajax()` - Check if AJAX request - `req.wantsJson()` - Check if wants JSON - `req.expectsJson()` - Check if expects JSON - `req.isInertia()` - Check if Inertia request - `req.file()` - Get uploaded file - `req.hasFile()` - Check if file exists - `req.store()` - Store uploaded file - `req.bearerToken()` - Get bearer token - `req.userAgent()` - Get user agent - `req.cookie()` - Get cookie - `req.header()` - Get header - `req.isMethod()` - Check HTTP method - `req.fullUrl()` - Get full URL **AppResponse Extensions:** - `res.inertia()` - Render Inertia page - `res.inertiaRedirect()` - Inertia redirect - `res.redirectBack()` - Redirect to previous URL - `res.with()` - Set flash message and chain --- # Validation JCC Express MVC uses the [validatorjs](https://www.npmjs.com/package/validatorjs) package for validation, with custom validation methods registered by the framework. This provides a powerful, flexible validation system that works seamlessly with Express requests. ## Introduction JCC Express MVC provides several different approaches to validate your application's incoming data: 1. **Inline Validation** - Using `req.validate()` directly in controllers 2. **Form Request Classes** - Encapsulating validation logic in dedicated request classes Both approaches use the same validation rules and syntax. ## Inline Validation You can validate request data directly in your controllers using the `req.validate()` method: ```typescript // In route closures, TypeScript automatically infers types - no imports needed Route.post("/users", async (req, res) => { // Validate request data await req.validate({ name: "required|string|max:255", email: "required|email|unique:users,email", // Can use table name or model name password: "required|string|min:8", }); // Get validated data const validated = await req.validated(); // Create user with validated data const user = await User.create(validated); return res.json({ user }); }); ``` ### Validation Rules Syntax Rules can be specified as: - **String format**: `"required|email|min:5"` - **Array format**: `["required", "email", "min:5"]` ```typescript // String format (pipe-separated) await req.validate({ name: "required|string|max:255", email: "required|email", }); // Array format await req.validate({ name: ["required", "string", "max:255"], email: ["required", "email"], }); ``` ### Custom Error Messages You can provide custom error messages: ```typescript await req.validate( { name: "required|string", email: "required|email", }, { "name.required": "The name field is required.", "email.email": "That doesn't look like an email address.", } ); ``` ## Form Request Validation For more complex validation scenarios, you may wish to create a "form request". Form requests are custom request classes that encapsulate their own validation and authorization logic. ### Creating Form Requests To create a form request class, use the `make:request` Artisan command: ```bash bun artisanNode make:request StoreUserRequest ``` The generated class will be placed in the `app/Http/Requests` directory. ### Writing Form Requests Form requests are custom request classes that extend `FormRequest`. Let's look at an example form request: ```typescript import { FormRequest } from "jcc-express-mvc/Core/FormRequest"; export class StoreUserRequest extends FormRequest { /** * Get the validation rules that apply to the request. */ async rules() { await this.validate({ name: "required|string|max:255", email: "required|email|unique:users,email", // Can use table name or model name password: "required|string|min:8", }); } /** * Get custom messages for validator errors. */ messages() { return { "name.required": "The name field is required.", "email.unique": "This email is already taken.", }; } } ``` ### Using Form Requests Now you can type-hint the form request in your controller method. The incoming request data will be automatically validated before the controller method is called: ```typescript import { StoreUserRequest } from "@/Http/Requests/StoreUserRequest"; import { httpContext } from "jcc-express-mvc"; class UserController { async store(request: StoreUserRequest, { res } = httpContext) { // The incoming request has been validated... // You can access validated data via request.body or request.validated() const validated = await request.validated(); const { name, email } = validated; // Create the user... const user = await User.create(validated); return res.json({ user }); } } ``` ## Available Validation Rules JCC Express MVC provides a wide variety of validation rules. The framework uses [validatorjs](https://www.npmjs.com/package/validatorjs) as the base validation library, which means all validatorjs rules are available. Additionally, the framework registers custom validation methods for database operations and other framework-specific needs. ### All Validatorjs Rules Since JCC Express MVC uses validatorjs, all standard validatorjs rules are available. Refer to the [validatorjs documentation](https://www.npmjs.com/package/validatorjs) for the complete list of available rules. Common rules include: - `accepted`, `active_url`, `after:date`, `after_or_equal:date` - `alpha`, `alpha_dash`, `alpha_num` - `array`, `before:date`, `before_or_equal:date` - `between:min,max`, `boolean` - `confirmed`, `date`, `date_equals:date`, `date_format:format` - `different:field`, `digits:value`, `digits_between:min,max` - `dimensions`, `distinct`, `email`, `exists:table,column` - `file`, `filled`, `gt:field`, `gte:field` - `image`, `in:value1,value2`, `in_array:field` - `integer`, `ip`, `ipv4`, `ipv6` - `json`, `lt:field`, `lte:field` - `max:value`, `mimetypes`, `mimes` - `min:value`, `not_in:value1,value2` - `not_regex:pattern`, `nullable`, `numeric` - `present`, `regex:pattern`, `required` - `required_if:field,value`, `required_unless:field,value` - `required_with:field1,field2`, `required_with_all:field1,field2` - `required_without:field1,field2`, `required_without_all:field1,field2` - `same:field`, `size:value`, `string` - `timezone`, `unique:table,column`, `url`, `uuid` ### Framework Custom Rules The framework registers the following custom validation methods that extend validatorjs: - `unique:Model,column` - Database uniqueness validation - `nullable` - Allows null/empty values (field is optional) - `sometimes` - Only validates the field if it is present in the request - `file` - Validates that a file was uploaded - `image` - Validates that an image file was uploaded These custom rules are automatically registered and available for use in your validation rules. ### Basic Rules - `required` - The field must be present and not empty ### String Rules - `string` - The field must be a string - `min:value` - The field must have a minimum length/value - `max:value` - The field must have a maximum length/value - `alpha` - The field must contain only alphabetic characters - `alphaNum` - The field must contain only alphanumeric characters - `slug` - The field must be a valid slug ### Numeric Rules - `numeric` or `num` - The field must be numeric - `integer` or `int` - The field must be an integer - `float` - The field must be a float - `decimal` - The field must be a decimal number ### Email & URL Rules - `email` - The field must be a valid email address - `url` - The field must be a valid URL ### Comparison Rules - `same:field` - The field must match another field (e.g., password confirmation) - `confirmed` - The field must have a matching `{field}_confirmation` field ### Database Rules - `unique:Model,column` or `unique:table,column` - The field must be unique in the database - Example: `unique:User,email` - Checks if email is unique in User model - Example: `unique:users,email` - Checks if email is unique in users table - Example: `unique:users` - Checks if the field value is unique in users table (uses field name as column) - You can use either model class name (e.g., `User`) or table name (e.g., `users`) - If column is not specified, it defaults to the field name being validated ### Array & Object Rules - `array` - The field must be an array - `object` - The field must be an object ### Boolean Rules - `boolean` or `bool` - The field must be a boolean value ### Special Format Rules - `json` - The field must be valid JSON - `jwt` - The field must be a valid JWT token - `creditCard` - The field must be a valid credit card number - `phone` - The field must be a valid phone number - `postal:countryCode` - The field must be a valid postal code for the given country - `mongoId` - The field must be a valid MongoDB ObjectId ### File Rules - `file` - The field must be an uploaded file (validates file presence) - `image` - The field must be an uploaded image file (validates image file presence) **Note:** The framework automatically normalizes file and image fields during validation. When using `file` or `image` rules, the validator checks for file presence using `req.hasFile(field)`. ### Complete Rules Example ```typescript await req.validate({ // Basic name: "required|string|max:255", // Email with uniqueness check (can use table name or model name) email: "required|email|unique:users,email", // Password with confirmation password: "required|string|min:8", password_confirmation: "required|same:password", // Numeric age: "required|integer|min:18|max:100", price: "required|decimal|min:0", // Optional fields (nullable allows field to be empty) bio: "nullable|string|max:1000", // Conditional validation (only validates if field is present) phone: "sometimes|phone", // Arrays and objects tags: "required|array", metadata: "nullable|object", // Special formats website: "nullable|url", phone_number: "nullable|phone", postal_code: "nullable|postal:US", // File uploads document: "required|file", // Required file upload avatar: "nullable|image", // Optional image upload resume: "sometimes|file", // Only validate if file is provided }); ``` ### File Validation Example Here's a complete example of validating file uploads: ```typescript // In route closures, TypeScript automatically infers types - no imports needed Route.post("/profile", async (req, res) => { await req.validate({ name: "required|string|max:255", avatar: "nullable|image", // Optional profile image resume: "required|file", // Required resume file }); const validated = await req.validated(); // Handle file uploads if (req.hasFile("avatar")) { const avatarPath = req.file("avatar").store("avatars"); // Save avatar path to database } if (req.hasFile("resume")) { const resumePath = req.file("resume").store("resum