# Configuration LaraNode uses a file-based configuration system with environment variable support. ## Configuration Files Configuration files live in `src/config/` and export configuration objects: ```typescript // src/config/database.config.ts export default { connection: process.env.DB_CONNECTION || "mysql", mysql: { host: process.env.DB_HOST || "127.0.0.1", port: parseInt(process.env.DB_PORT || "3306"), user: process.env.DB_USER || "root", password: process.env.DB_PASSWORD || "", database: process.env.DB_NAME || "vest", poolLimit: parseInt(process.env.DB_POOL_LIMIT || "10"), }, mongodb: { uri: process.env.MONGO_URI || "mongodb://localhost:27017", replicaSet: process.env.MONGO_REPLICA_SET, }, }; ``` ## Environment Variables Create a `.env` file in your project root: ```dotenv # Application NODE_ENV=development APP_KEY=base64:your-key-here APP_PORT=3000 # Database DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_USER=root DB_PASSWORD=secret DB_NAME=vest_app # Cache CACHE_DRIVER=file CACHE_PREFIX=vest_ # Queue QUEUE_CONNECTION=sync # Redis REDIS_HOST=127.0.0.1 REDIS_PORT=6379 REDIS_PASSWORD= # Mail MAIL_DRIVER=smtp MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 MAIL_USERNAME= MAIL_PASSWORD= MAIL_FROM_ADDRESS=hello@example.com MAIL_FROM_NAME="LaraNode App" # Broadcast BROADCAST_DRIVER=websocket ``` ## Accessing Configuration Use the `config()` helper to access configuration values: ```typescript import { config, hasConfig, setConfig } from "@lara-node/core"; // Get a value (dot notation) const dbHost = config("database.mysql.host"); // Get with default const port = config("app.port", 3000); // Check if exists if (hasConfig("cache.redis")) { // ... } // Set a value setConfig("app.debug", true); ``` ## Configuration by Package ### Core ```typescript // config/app.config.ts export default { name: "LaraNode App", env: process.env.NODE_ENV || "development", debug: process.env.NODE_ENV === "development", key: process.env.APP_KEY, port: parseInt(process.env.APP_PORT || "3000"), }; ``` ### Database ```typescript // config/database.config.ts export default { connection: process.env.DB_CONNECTION || "mysql", mysql: { host: process.env.DB_HOST, port: parseInt(process.env.DB_PORT || "3306"), user: process.env.DB_USER, password: process.env.DB_PASSWORD, database: process.env.DB_NAME, }, mongodb: { uri: process.env.MONGO_URI, }, }; ``` ### Cache ```typescript // config/cache.config.ts export default { driver: process.env.CACHE_DRIVER || "file", prefix: process.env.CACHE_PREFIX || "vest_", path: process.env.CACHE_PATH || "./storage/cache", redis: { host: process.env.REDIS_HOST || "127.0.0.1", port: parseInt(process.env.REDIS_PORT || "6379"), password: process.env.REDIS_PASSWORD, }, }; ``` ### Queue ```typescript // config/queue.config.ts export default { default: process.env.QUEUE_CONNECTION || "sync", connections: { sync: { driver: "sync" }, database: { driver: "database", table: "jobs" }, redis: { driver: "redis", host: process.env.REDIS_HOST, port: parseInt(process.env.REDIS_PORT || "6379"), }, }, }; ``` ### Mail ```typescript // config/mail.config.ts export default { driver: process.env.MAIL_DRIVER || "log", from: { address: process.env.MAIL_FROM_ADDRESS, name: process.env.MAIL_FROM_NAME, }, smtp: { host: process.env.MAIL_HOST, port: parseInt(process.env.MAIL_PORT || "587"), secure: false, auth: { user: process.env.MAIL_USERNAME, pass: process.env.MAIL_PASSWORD, }, }, }; ``` ### Horizon ```typescript // config/horizon.config.ts export default { domain: "", path: "/horizon", environments: { production: { supervisor: { maxProcesses: 10, balance: "auto", }, }, local: { supervisor: { maxProcesses: 3, }, }, }, }; ``` ### Telescope ```typescript // config/telescope.config.ts export default { path: "/telescope", enabled: process.env.NODE_ENV !== "production", maxEntries: 1000, pruneHours: 48, }; ``` ## Next Steps - [Service Providers](https://laranode.doitrixtech.co.ke/guide/service-providers) -- Learn about service providers - [Core Package](https://laranode.doitrixtech.co.ke/packages/core) -- Deep dive into the core - [Database](https://laranode.doitrixtech.co.ke/packages/db) -- Configure your database # Dependency Injection LaraNode uses a powerful IoC (Inversion of Control) container for dependency injection, inspired by Laravel's container. ## The Container The container is the heart of LaraNode's dependency injection system: ```typescript import { container, Container } from "@lara-node/core"; // Get the singleton instance const container = container; // Or create your own const myContainer = new Container(); ``` ## Binding Services ### Simple Binding ```typescript container.bind("logger", () => new Logger()); const logger = container.make("logger"); ``` ### Singleton Binding Singletons are resolved once and cached: ```typescript container.singleton(DatabaseService, () => { return new DatabaseService(config("database")); }); // Same instance returned each time const db1 = container.make(DatabaseService); const db2 = container.make(DatabaseService); // db1 === db2 ``` ### Instance Binding Bind an existing instance: ```typescript const expressApp = express(); container.instance("express", expressApp); ``` ### Alias Binding Create aliases for existing bindings: ```typescript container.bind("cache", () => new CacheManager()); container.alias("cache", CacheManager); // Both resolve to the same service container.make("cache"); container.make(CacheManager); ``` ## Resolving Services ### Using `make()` ```typescript const service = container.make(UserService); ``` ### Using `app()` Helper ```typescript import { app } from "@lara-node/core"; // Resolve a service const service = app(UserService); // Get the container const container = app(); ``` ## Automatic Resolution The container can automatically resolve class dependencies: ```typescript import { Injectable } from "@lara-node/core"; @Injectable() class EmailService { constructor(private mailer: MailManager) {} sendWelcomeEmail(user: User) { this.mailer.to(user.email).send(); } } // Container automatically resolves MailManager const emailService = container.make(EmailService); ``` ## Injectable Decorator Mark classes for automatic dependency injection: ```typescript import { Injectable } from "@lara-node/core"; @Injectable() class UserService { constructor( private db: DatabaseService, private cache: CacheManager, private events: EventDispatcher, ) {} } ``` ## Resolving Callbacks Run callbacks when a type is resolved: ```typescript container.resolving(UserService, (service) => { service.initialize(); }); ``` ## Binding Contextual Values Bind different implementations based on context: ```typescript container .when(DatabaseService) .needs(CacheDriver) .give(() => new RedisCache()); ``` ## Practical Example ```typescript // src/app/Providers/AppServiceProvider.ts import { ServiceProvider } from "@lara-node/core"; import { UserService } from "../Services/UserService"; import { AuthService } from "../Services/AuthService"; export class AppServiceProvider extends ServiceProvider { register() { // Bind services this.app.singleton(UserService, () => { return new UserService(this.app.make(DatabaseService), this.app.make(CacheManager)); }); this.app.singleton(AuthService, () => { return new AuthService(this.app.make(UserService), this.app.make(JwtService)); }); } } // Usage in controller import { Injectable } from "@lara-node/core"; @Injectable() class UserController { constructor(private userService: UserService) {} async index() { return this.userService.all(); } } ``` ## Next Steps - [Service Providers](https://laranode.doitrixtech.co.ke/guide/service-providers) -- Learn about service providers - [Core Container](https://laranode.doitrixtech.co.ke/packages/core/container) -- Deep dive into the container - [Application](https://laranode.doitrixtech.co.ke/packages/core/application) -- Learn about the Application class # Facades Facades provide a static-like interface to services registered in the container, enabling clean, expressive syntax. ## What are Facades? Facades are proxy classes that redirect method calls to the underlying service instance in the container. They give you the convenience of static methods with the flexibility of dependency injection. ## Using Facades ### Cache Facade ```typescript import { Cache } from "@lara-node/cache"; // Store a value Cache.set("key", "value", 3600); // Retrieve a value const value = Cache.get("key"); // Get with default const value = Cache.get("key", "default"); // Remember (get or set) const users = Cache.remember("users", 3600, async () => { return User.all(); }); ``` ### DB Facade ```typescript import { DB } from "@lara-node/db"; // Query builder const users = DB.table("users").where("active", true).get(); // Raw query const results = DB.select("SELECT * FROM users WHERE active = ?", [true]); // Transaction await DB.transaction(async (db) => { await db.table("users").insert({ name: "John" }); await db.table("profiles").insert({ user_id: 1 }); }); ``` ### Broadcast Facade ```typescript import { Broadcast } from "@lara-node/events"; Broadcast.channel("chat").emit("message", data); ``` ### Mail Facade ```typescript import { Mail } from "@lara-node/mail"; Mail.to("user@example.com").send(new WelcomeMail()); ``` ### RateLimiter Facade ```typescript import { RateLimiter } from "@lara-node/cache"; if (RateLimiter.tooManyAttempts("key", 60)) { throw new RateLimitExceededException(); } RateLimiter.hit("key", 60); ``` ## How Facades Work Facades work by: 1. Looking up the service in the container 2. Redirecting method calls to the resolved instance 3. Caching the resolved instance for subsequent calls ## Creating Custom Facades You can create your own facades: ```typescript import { Container } from "@lara-node/core"; export const MyService = { get method() { return container.make(MyServiceClass).method.bind(container.make(MyServiceClass)); }, }; ``` ## Facade vs Direct Injection | Facade | Direct Injection | | --------------------- | --------------------------------------------------- | | `Cache.get('key')` | `const cache = app(CacheManager); cache.get('key')` | | Cleaner syntax | More explicit | | Good for quick access | Better for testing | | | Better for type inference | ## Next Steps - [Container](https://laranode.doitrixtech.co.ke/packages/core/container) -- Learn about the IoC container - [Cache](https://laranode.doitrixtech.co.ke/packages/cache) -- Cache facade usage - [Database](https://laranode.doitrixtech.co.ke/packages/db/facade) -- DB facade usage # Getting Started This guide will help you create your first LaraNode application from scratch. ## Prerequisites - **Node.js** >= 18.0.0 - **pnpm** >= 8.0.0 (recommended) or npm/yarn - **TypeScript** >= 5.0.0 ## Quick Start The fastest way to get started is using the `create-laranode` scaffolding tool: ```bash pnpm create laranode my-app cd my-app pnpm install ``` This creates a complete LaraNode application with: - Configured TypeScript project - Service providers and bootstrapping - Sample models, controllers, and services - Environment configuration - Migration and seeder setup ## Manual Setup If you prefer to set up manually: ### 1. Initialize Project ```bash mkdir my-app && cd my-app pnpm init ``` ### 2. Install Core Dependencies ```bash pnpm add @lara-node/core @lara-node/router @lara-node/db express reflect-metadata pnpm add -D typescript @types/node @types/express ``` ### 3. Configure TypeScript Create `tsconfig.json`: ```json { "compilerOptions": { "target": "ES2020", "module": "commonjs", "lib": ["ES2020"], "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, "moduleResolution": "node", "resolveJsonModule": true, "declaration": true, "declarationMap": true, "sourceMap": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] } ``` ### 4. Create Application Bootstrap Create `src/bootstrap/app.ts`: ```typescript import { Application, Container } from "@lara-node/core"; import { AppServiceProvider } from "../app/Providers/AppServiceProvider"; import { RouteServiceProvider } from "../app/Providers/RouteServiceProvider"; const container = new Container(); const app = new Application(container); app.register(AppServiceProvider); app.register(RouteServiceProvider); export { app }; ``` ### 5. Create Service Provider Create `src/app/Providers/AppServiceProvider.ts`: ```typescript import { ServiceProvider } from "@lara-node/core"; export class AppServiceProvider extends ServiceProvider { register() { // Register bindings } boot() { // Boot services } } ``` ### 6. Create Server Entry Create `src/server.ts`: ```typescript import { app } from "./bootstrap/app"; import "reflect-metadata"; async function bootstrap() { await app.boot(); await app.listen(3000); console.log("Server running on http://localhost:3000"); } bootstrap(); ``` ### 7. Start Development ```bash pnpm add -D tsx pnpm exec tsx src/server.ts ``` ## Using the CLI LaraNode includes an Artisan-style CLI for common tasks: ```bash # Start development server pnpm exec artisan serve # List routes pnpm exec artisan route:list # Run migrations pnpm exec artisan migrate # Clear cache pnpm exec artisan cache:clear ``` ## Next Steps - [Installation](https://laranode.doitrixtech.co.ke/guide/installation) -- Detailed installation guide - [Project Structure](https://laranode.doitrixtech.co.ke/guide/project-structure) -- Understand the directory layout - [Configuration](https://laranode.doitrixtech.co.ke/guide/configuration) -- Configure your application # Installation This guide covers different ways to install and set up LaraNode in your project. ## Using Create LaraNode (Recommended) The fastest way to start a new project: ```bash pnpm create vest my-app cd my-app pnpm install ``` This scaffolds a complete application with all recommended packages. ## Installing Individual Packages You can install LaraNode packages individually: ```bash # Core (required) pnpm add @lara-node/core # Database pnpm add @lara-node/db # Routing pnpm add @lara-node/router # Authentication pnpm add @lara-node/auth # Validation pnpm add @lara-node/validator # Cache pnpm add @lara-node/cache # Queue pnpm add @lara-node/queue # Events pnpm add @lara-node/events # Mail pnpm add @lara-node/mail # Middlewares pnpm add @lara-node/middlewares # Date handling pnpm add @lara-node/carbon # Console CLI pnpm add @lara-node/console # Monitoring pnpm add @lara-node/horizon @lara-node/telescope ``` ## Peer Dependencies Most LaraNode packages require `@lara-node/core` as a peer dependency. Make sure to install it: ```bash pnpm add @lara-node/core reflect-metadata ``` ## Required Dependencies LaraNode requires these base packages: ```bash pnpm add express cors reflect-metadata ``` ## TypeScript Configuration LaraNode uses decorators and requires specific TypeScript settings: ```json { "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true, "moduleResolution": "node" } } ``` ## Environment Setup Create a `.env` file in your project root: ```dotenv # Application NODE_ENV=development APP_KEY=base64:your-generated-key-here APP_PORT=3000 # Database DB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_USER=root DB_PASSWORD= DB_NAME=vest_app # Cache CACHE_DRIVER=file # Queue QUEUE_CONNECTION=sync # Mail MAIL_DRIVER=log ``` Generate an application key: ```bash pnpm exec artisan key:generate ``` ## Verify Installation Create a simple test: ```typescript import { Application, Container } from "@lara-node/core"; const container = new Container(); const app = new Application(container); await app.boot(); console.log("LaraNode is working!"); ``` ## Next Steps - [Project Structure](https://laranode.doitrixtech.co.ke/guide/project-structure) -- Understand the directory layout - [Configuration](https://laranode.doitrixtech.co.ke/guide/configuration) -- Configure your application - [Core Package](https://laranode.doitrixtech.co.ke/packages/core) -- Learn about the core container # Introduction LaraNode is a **Laravel-inspired Node.js framework** built on top of Express.js. It brings the elegant developer experience of Laravel to the Node.js ecosystem, with a focus on developer productivity, clean architecture, and convention over configuration. ## Why LaraNode? If you love Laravel's expressive syntax and architecture but need to work in the Node.js ecosystem, LaraNode is for you. It provides: - **Familiar patterns** -- Service providers, facades, Eloquent ORM, Artisan CLI - **TypeScript first** -- Full TypeScript support with decorators and type inference - **Modular architecture** -- 16 focused packages you can use independently - **Production ready** -- Queue workers, caching, rate limiting, monitoring dashboards ## Philosophy LaraNode follows Laravel's core principles: 1. **Developer Experience** -- Expressive, readable APIs that make sense 2. **Convention over Configuration** -- Sensible defaults with flexibility to override 3. **Batteries Included** -- Everything you need out of the box 4. **Elegant Architecture** -- IoC container, service providers, facades ## Core Concepts ### Service Providers Service providers are the central place for configuring your application. They register bindings in the IoC container and boot services: ```typescript import { ServiceProvider } from "@lara-node/core"; export class AppServiceProvider extends ServiceProvider { register() { this.app.bind("myService", () => new MyService()); } boot() { const service = this.app.make("myService"); service.initialize(); } } ``` ### IoC Container The container manages class dependencies and performs dependency injection: ```typescript import { container, Injectable } from "@lara-node/core"; @Injectable() class UserService { constructor(private db: DatabaseService) {} } const service = container.make(UserService); ``` ### Eloquent Models Models provide an expressive way to interact with your database: ```typescript import { Model, use } from "@lara-node/db"; import { SoftDeletes, Timestamps } from "@lara-node/db"; @use(SoftDeletes, Timestamps) class User extends Model { static table = "users"; static fillable = ["name", "email", "password"]; static hidden = ["password"]; } const users = await User.where("active", true).get(); ``` ### Routing Define routes with a fluent builder or controller decorators: ```typescript import { Route } from "@lara-node/router"; @Route("/api/users") class UserController { @Route.get("/") async index() { return User.all(); } @Route.post("/") async store(req: Request) { return User.create(req.body); } } ``` ## Package Ecosystem LaraNode is organized into focused packages: | Package | Description | | ------------------------ | --------------------------------------------- | | `@lara-node/core` | IoC container, Application, Service Providers | | `@lara-node/db` | Eloquent ORM with MySQL & MongoDB | | `@lara-node/router` | Expressive routing with decorators | | `@lara-node/auth` | JWT authentication & password hashing | | `@lara-node/validator` | 50+ validation rules | | `@lara-node/cache` | Multi-driver caching & rate limiting | | `@lara-node/queue` | Job queues with workers | | `@lara-node/events` | Event dispatcher & broadcasting | | `@lara-node/mail` | Multi-driver email system | | `@lara-node/middlewares` | Pre-built middleware | | `@lara-node/carbon` | Laravel Carbon-inspired dates | | `@lara-node/console` | Artisan-style CLI | | `@lara-node/horizon` | Queue monitoring dashboard | | `@lara-node/telescope` | Debug dashboard | ## Next Steps - [Getting Started](https://laranode.doitrixtech.co.ke/guide/getting-started) -- Create your first LaraNode application - [Installation](https://laranode.doitrixtech.co.ke/guide/installation) -- Install LaraNode in your project - [Project Structure](https://laranode.doitrixtech.co.ke/guide/project-structure) -- Understand the directory layout # MCP & AI Integration LaraNode comes with built-in support for the Model Context Protocol (MCP). This allows AI assistants like Claude, Cursor, and ChatGPT to interact directly with your LaraNode application. ## MCP Server The documentation site exposes an MCP server at `/mcp` that AI assistants can connect to. This provides tools for: - **Listing documentation pages** — Discover all available documentation - **Reading page content** — Retrieve specific documentation pages - **Searching documentation** — Find relevant information quickly ## Connecting AI Assistants ### Cursor 1. Open Cursor Settings 2. Navigate to Features → MCP Servers 3. Add a new MCP server with URL: `https://laranode.doitrix.co.ke/mcp` ### VS Code 1. Install the MCP extension 2. Add the server URL: `https://laranode.doitrix.co.ke/mcp` ### Claude Desktop Add to your `claude_desktop_config.json`: ```json { "mcpServers": { "laranode-docs": { "url": "https://laranode.doitrix.co.ke/mcp" } } } ``` ## LLM Documentation LaraNode provides LLM-optimized documentation: - [llms.txt](https://laranode.doitrixtech.co.ke/llms.txt) — Concise documentation summary for LLMs - [llms-full.txt](https://laranode.doitrixtech.co.ke/llms-full.txt) — Complete documentation for LLMs ## Agent Skills LaraNode includes a comprehensive set of [Agent Skills](https://www.skills.sh/){rel=""nofollow""} — specialized instructions that help AI assistants work with the framework more effectively. Each skill provides detailed API references, code patterns, and common tasks for a specific package. ### Available Skills The skills are located in the root `skills/` directory and cover every LaraNode package: | Skill Name | Package | Purpose | | --------------------------- | ------------------------ | ----------------------------------------------- | | `laranode-framework` | — | Framework overview, philosophy, architecture | | `laranode-core` | `@lara-node/core` | IoC container, Application, Service Providers | | `laranode-db` | `@lara-node/db` | Eloquent ORM, Models, Migrations, Query Builder | | `laranode-router` | `@lara-node/router` | Routing, Controllers, OpenAPI generation | | `laranode-auth` | `@lara-node/auth` | JWT auth, password hashing, token encryption | | `laranode-validator` | `@lara-node/validator` | 50+ validation rules, custom rules | | `laranode-cache` | `@lara-node/cache` | Multi-driver caching, rate limiting | | `laranode-queue` | `@lara-node/queue` | Job queue, workers, scheduler | | `laranode-events` | `@lara-node/events` | Event dispatcher, listeners, broadcasting | | `laranode-mail` | `@lara-node/mail` | Multi-driver email, Mailable classes | | `laranode-middlewares` | `@lara-node/middlewares` | Pre-built HTTP middleware | | `laranode-carbon` | `@lara-node/carbon` | Date/time manipulation | | `laranode-console` | `@lara-node/console` | Artisan CLI, 40+ commands | | `laranode-horizon` | `@lara-node/horizon` | Queue monitoring dashboard | | `laranode-telescope` | `@lara-node/telescope` | Debug & observability dashboard | | `laranode-exports` | `@lara-node/exports` | PDF, Excel & CSV export utilities | | `laranode-csv` | `@lara-node/csv` | CSV generation, parsing, streaming | | `laranode-excel` | `@lara-node/excel` | Excel file generation & parsing | | `laranode-html` | `@lara-node/html` | HTML rendering, templating, sanitization | | `laranode-pdf` | `@lara-node/pdf` | PDF generation via Puppeteer | | `laranode-xml` | `@lara-node/xml` | XML building, parsing, serialization | | `laranode-create-lara-node` | `create-lara-node` | Project scaffolding | ### Setting Up Skills To use Agent Skills with your AI assistant, install the skills collection: ```bash npx skills add laranode/lara-node ``` Or run the [skills.sh](https://www.skills.sh/){rel=""nofollow""} script directly: ```bash curl -fsSL https://skills.sh/install.sh | bash skills link skills ``` After installation, the skills are available at the root `skills/` directory. ### Skill Structure Each skill is a directory containing a `SKILL.md` file: ```text skills/ ├── laranode-framework/SKILL.md # Root framework skill ├── laranode-core/SKILL.md # Core package skill ├── laranode-db/SKILL.md # Database ORM skill ├── laranode-router/SKILL.md # Routing skill └── ... # One per package ``` Skills follow the [Agent Skills specification](https://agentskills.io/specification){rel=""nofollow""} with YAML frontmatter (name, description) and markdown body with step-by-step instructions, code examples, and common patterns. ### Using Skills in AI Assistants Skills are automatically loaded when your AI assistant connects to the LaraNode repository through any [skills.sh-compatible client](https://www.skills.sh/clients){rel=""nofollow""}. When you ask a question about a specific package, the corresponding skill activates to provide precise, contextual guidance. For example, asking "How do I set up model relationships?" activates the **DB skill**, which provides Eloquent relationship patterns. Asking "How do I define routes?" activates the **Router skill**. # Middleware Middleware provides a convenient mechanism for filtering HTTP requests entering your application. ## Overview LaraNode uses a Laravel-style middleware system with support for: - Global middleware - Middleware groups - Route middleware (aliases) - Priority middleware - Terminable middleware ## Creating Middleware Create a middleware class with a `handle` method: ```typescript import { Request, Response, NextFunction } from "express"; export class AuthMiddleware { async handle(req: Request, res: Response, next: NextFunction) { const token = req.headers.authorization?.replace("Bearer ", ""); if (!token) { return res.status(401).json({ error: "Unauthenticated" }); } try { req.user = verifyToken(token); next(); } catch { return res.status(401).json({ error: "Invalid token" }); } } } ``` ## Registering Middleware ### In a Provider ```typescript import { MiddlewareServiceProvider } from "@lara-node/core"; export class HttpMiddlewareProvider extends MiddlewareServiceProvider { registerMiddleware() { return { aliases: { auth: AuthMiddleware, throttle: ThrottleMiddleware, admin: AdminMiddleware, }, groups: { api: ["throttle:60,1", "auth"], web: ["session", "csrf"], }, priority: ["throttle"], }; } } ``` ### Global Middleware Global middleware runs on every request: ```typescript app.useGlobalMiddleware(CorsMiddleware); ``` ## Using Middleware in Routes ### By Alias ```typescript Route.get("/profile", UserController.profile).middleware("auth"); ``` ### Multiple Middleware ```typescript Route.get("/admin", AdminController.index).middleware(["auth", "admin"]); ``` ### Middleware Groups ```typescript Route.group(() => { Route.get("/users", UserController.index); Route.post("/users", UserController.store); }).middleware("api"); ``` ### Without Middleware Remove middleware from a group: ```typescript Route.group(() => { Route.get("/public", PublicController.index); }) .middleware("api") .withoutMiddleware("auth"); ``` ## Route Middleware Parameters Pass parameters to middleware: ```typescript Route.get("/api/data", DataController.index).middleware("throttle:60,1"); // 60 requests per 1 minute ``` ## Priority Middleware Priority middleware runs before other middleware: ```typescript registerMiddleware() { return { priority: ['cors', 'throttle'], } } ``` ## Terminable Middleware Terminable middleware can run after the response is sent: ```typescript export class TerminatingMiddleware { async handle(req: Request, res: Response, next: NextFunction) { req.startTime = Date.now(); next(); } async terminate(req: Request, res: Response) { const duration = Date.now() - req.startTime; console.log(`Request took ${duration}ms`); } } ``` ## Built-in Middleware LaraNode provides several built-in middleware: | Middleware | Description | | ---------------------------- | --------------------- | | `AuthMiddleware` | JWT authentication | | `ThrottleMiddleware` | Rate limiting | | `RequestLoggerMiddleware` | Request logging | | `ErrorHandlerMiddleware` | Error handling | | `ValidatorMiddleware` | Request validation | | `ResponseExtenderMiddleware` | Auto-serialize models | ## Next Steps - [Router Middleware](https://laranode.doitrixtech.co.ke/packages/router/middleware) -- Route-level middleware - [Built-in Middleware](https://laranode.doitrixtech.co.ke/packages/middlewares/built-in) -- Pre-built middleware - [Auth Middleware](https://laranode.doitrixtech.co.ke/packages/auth/middleware) -- Authentication middleware # Project Structure A typical LaraNode application follows a Laravel-inspired directory structure. ## Default Structure When you create a new project with `create-vest`, you get: ```text my-app/ ├── src/ │ ├── app/ │ │ ├── Events/ # Event classes │ │ ├── Http/ │ │ │ ├── Controllers/ # HTTP controllers │ │ │ └── Kernel.ts # HTTP kernel │ │ ├── Jobs/ # Queue job classes │ │ ├── Listeners/ # Event listeners │ │ ├── Mail/ # Mailable classes │ │ ├── Middleware/ # Custom middleware │ │ ├── Models/ # Database models │ │ ├── Observers/ # Model observers │ │ ├── Providers/ # Service providers │ │ └── Services/ # Business logic services │ ├── bootstrap/ │ │ └── app.ts # Application bootstrap │ ├── config/ # Configuration files │ │ ├── app.config.ts │ │ ├── database.config.ts │ │ ├── cache.config.ts │ │ ├── queue.config.ts │ │ ├── mail.config.ts │ │ └── horizon.config.ts │ ├── database/ │ │ ├── migrations/ # Database migrations │ │ └── seeders/ # Database seeders │ ├── routes/ │ │ └── api.ts # Route definitions │ ├── artisan.ts # CLI entry point │ └── server.ts # HTTP server entry point ├── .env # Environment variables ├── .env.example # Example environment file ├── package.json └── tsconfig.json ``` ## Directory Descriptions ### `src/app/` Contains your application's business logic: | Directory | Purpose | | ------------------- | ----------------------------------------- | | `Events/` | Event classes that can be dispatched | | `Http/Controllers/` | HTTP controllers handling requests | | `Http/Kernel.ts` | HTTP kernel with middleware configuration | | `Jobs/` | Queueable job classes | | `Listeners/` | Event listener classes | | `Mail/` | Mailable email classes | | `Middleware/` | Custom HTTP middleware | | `Models/` | Database model classes | | `Observers/` | Model observer classes | | `Providers/` | Service provider classes | | `Services/` | Business logic service classes | ### `src/bootstrap/` Application bootstrapping: - `app.ts` -- Creates the Application instance, registers service providers ### `src/config/` Configuration files for each package: - `app.config.ts` -- Application settings - `database.config.ts` -- Database connection settings - `cache.config.ts` -- Cache driver configuration - `queue.config.ts` -- Queue connection settings - `mail.config.ts` -- Mail driver settings - `horizon.config.ts` -- Horizon queue monitoring ### `src/database/` Database migrations and seeders: - `migrations/` -- Timestamp-prefixed migration files - `seeders/` -- Database seeder classes ### `src/routes/` Route definitions: - `api.ts` -- API route definitions - `web.ts` -- Web route definitions (if applicable) ### Entry Points - `src/server.ts` -- HTTP server entry point - `src/artisan.ts` -- CLI entry point ## Entry Point Files ### server.ts ```typescript import { app } from "./bootstrap/app"; import "reflect-metadata"; async function bootstrap() { await app.boot(); await app.listen(3000); console.log("Server running on http://localhost:3000"); } bootstrap(); ``` ### artisan.ts ```typescript import { Kernel } from "@lara-node/console"; import "reflect-metadata"; const kernel = new Kernel(); kernel.handle(process.argv); ``` ## Customizing Structure You can customize the structure by modifying your service providers. The framework is flexible and doesn't enforce a strict directory layout. ## Next Steps - [Configuration](https://laranode.doitrixtech.co.ke/guide/configuration) -- Configure your application - [Service Providers](https://laranode.doitrixtech.co.ke/guide/service-providers) -- Learn about service providers - [Core Package](https://laranode.doitrixtech.co.ke/packages/core) -- Deep dive into the core # Service Providers Service providers are the central place to configure and bootstrap your LaraNode application. They are the bridge between your application code and the IoC container. ## Overview Every LaraNode application has service providers that register services in the container. When you run `create-vest`, you get `AppServiceProvider` and `RouteServiceProvider` by default. ## Creating a Provider Extend the `ServiceProvider` abstract class: ```typescript import { ServiceProvider } from "@lara-node/core"; export class AppServiceProvider extends ServiceProvider { register() { // Register bindings in the container } boot() { // Boot services after all providers are registered } } ``` ## Lifecycle Methods ### `register()` Called first. Use this to bind services into the container: ```typescript register() { // Bind a singleton this.app.singleton(DatabaseService, () => { return new DatabaseService(config('database')) }) // Bind a transient service this.app.bind(Logger, () => new Logger()) // Bind with a string alias this.app.bind('mailer', () => new MailManager()) } ``` ### `boot()` Called after all providers have been registered. Use this to access other services: ```typescript boot() { // All services are now available const db = this.app.make(DatabaseService) const router = this.app.make(RouterBuilder) // Configure routes this.registerRoutes(router) } ``` ### Lifecycle Hooks You can hook into the boot process: ```typescript booting(callback: () => void) { // Called before boot } booted(callback: () => void) { // Called after boot } ``` ## Registering Providers Register providers in your application bootstrap: ```typescript // src/bootstrap/app.ts import { Application, Container } from "@lara-node/core"; import { AppServiceProvider } from "../app/Providers/AppServiceProvider"; import { RouteServiceProvider } from "../app/Providers/RouteServiceProvider"; import { CacheServiceProvider } from "@lara-node/cache"; import { QueueServiceProvider } from "@lara-node/queue"; const container = new Container(); const app = new Application(container); app.register(AppServiceProvider); app.register(RouteServiceProvider); app.register(CacheServiceProvider); app.register(QueueServiceProvider); export { app }; ``` ## Provider with Middleware Providers can register middleware aliases: ```typescript import { MiddlewareServiceProvider } from "@lara-node/core"; export class HttpMiddlewareProvider extends MiddlewareServiceProvider { registerMiddleware() { return { aliases: { auth: AuthMiddleware, throttle: ThrottleMiddleware, admin: AdminMiddleware, }, groups: { api: ["throttle", "auth"], web: ["session"], }, priority: ["auth", "throttle"], }; } } ``` ## Auto-Discovery with @Provider Use the `@Provider()` decorator for auto-discovery: ```typescript import { Provider } from "@lara-node/core"; @Provider() export class EventServiceProvider extends ServiceProvider { register() { // Auto-discovered } } ``` ## Middleware Service Provider For registering middleware, extend `MiddlewareServiceProvider`: ```typescript import { MiddlewareServiceProvider } from "@lara-node/core"; export class MyMiddlewareProvider extends MiddlewareServiceProvider { registerMiddleware() { return { // Middleware aliases (used in routes) aliases: { auth: AuthMiddleware, cors: CorsMiddleware, }, // Middleware groups groups: { api: ["cors", "auth"], web: ["session", "csrf"], }, // Priority middleware (runs first) priority: ["cors"], }; } } ``` ## Conditional Registration Register services based on environment: ```typescript register() { if (config('app.env') === 'production') { this.app.singleton(CacheDriver, () => new RedisCache()) } else { this.app.singleton(CacheDriver, () => new FileCache()) } } ``` ## Next Steps - [Dependency Injection](https://laranode.doitrixtech.co.ke/guide/dependency-injection) -- Learn about DI - [Configuration](https://laranode.doitrixtech.co.ke/guide/configuration) -- Configure your application - [Core Package](https://laranode.doitrixtech.co.ke/packages/core) -- Deep dive into the core # Agent Skills LaraNode includes a comprehensive set of [Agent Skills](https://www.skills.sh/){rel=""nofollow""} — specialized instructions that help AI assistants work with the framework more effectively. Each skill is a `SKILL.md` file containing YAML frontmatter metadata and markdown body with API references, code patterns, and common tasks. ## Available Skills All skills are located in the root `skills/` directory of the LaraNode monorepo: ### Framework & Tooling | Skill Name | File | Description | | --------------------------- | ------------------------------------------- | ------------------------------------------------------------- | | `laranode-framework` | `skills/laranode-framework/SKILL.md` | Framework overview, philosophy, architecture, getting started | | `laranode-create-lara-node` | `skills/laranode-create-lara-node/SKILL.md` | Project scaffolding with `pnpm create laranode` | ### Core Infrastructure | Skill Name | Package | Description | | ---------------------- | -------------------------------------- | --------------------------------------------------------- | | `laranode-core` | `skills/laranode-core/SKILL.md` | IoC container, Application, Service Providers, Config | | `laranode-router` | `skills/laranode-router/SKILL.md` | Routing, controllers, decorators, OpenAPI | | `laranode-middlewares` | `skills/laranode-middlewares/SKILL.md` | Pre-built HTTP middleware (auth, logging, error handling) | | `laranode-console` | `skills/laranode-console/SKILL.md` | Artisan CLI, 40+ commands, custom commands | ### Data & Validation | Skill Name | Package | Description | | -------------------- | ------------------------------------ | ---------------------------------------------------------------------- | | `laranode-db` | `skills/laranode-db/SKILL.md` | Eloquent ORM, models, migrations, query builder, relationships, traits | | `laranode-validator` | `skills/laranode-validator/SKILL.md` | 50+ validation rules, custom rules, dot-notation | | `laranode-cache` | `skills/laranode-cache/SKILL.md` | Multi-driver caching (file, DB, Redis), rate limiting | ### Async & Communication | Skill Name | Package | Description | | ----------------- | --------------------------------- | ------------------------------------------ | | `laranode-queue` | `skills/laranode-queue/SKILL.md` | Job queue, workers, scheduler, failed jobs | | `laranode-events` | `skills/laranode-events/SKILL.md` | Event dispatcher, listeners, broadcasting | | `laranode-mail` | `skills/laranode-mail/SKILL.md` | Multi-driver email, Mailable classes | ### Security | Skill Name | Package | Description | | --------------- | ------------------------------- | ------------------------------------------ | | `laranode-auth` | `skills/laranode-auth/SKILL.md` | JWT auth, bcrypt hashing, token encryption | ### Date/Time | Skill Name | Package | Description | | ----------------- | --------------------------------- | --------------------------------- | | `laranode-carbon` | `skills/laranode-carbon/SKILL.md` | Carbon-inspired date/time library | ### File Export | Skill Name | Package | Description | | ------------------ | ---------------------------------- | ------------------------------------------------------ | | `laranode-exports` | `skills/laranode-exports/SKILL.md` | PDF, Excel & CSV exports | | `laranode-csv` | `skills/laranode-csv/SKILL.md` | CSV generation, parsing, streaming, manipulation | | `laranode-excel` | `skills/laranode-excel/SKILL.md` | Excel .xlsx generation & parsing | | `laranode-pdf` | `skills/laranode-pdf/SKILL.md` | PDF generation via Puppeteer | | `laranode-xml` | `skills/laranode-xml/SKILL.md` | XML building, parsing, serialization, RSS/Atom/sitemap | | `laranode-html` | `skills/laranode-html/SKILL.md` | HTML rendering, templating, minification, sanitization | ### Monitoring | Skill Name | Package | Description | | -------------------- | ------------------------------------ | ------------------------------- | | `laranode-horizon` | `skills/laranode-horizon/SKILL.md` | Queue monitoring dashboard | | `laranode-telescope` | `skills/laranode-telescope/SKILL.md` | Debug & observability dashboard | ## Setting Up Skills ### Using skills.sh Run the [skills.sh](https://www.skills.sh/){rel=""nofollow""} script in the LaraNode repository root: ### Using npx ```bash npx skills add VEN-LANG/vest ``` ## How Skills Work When an AI assistant connected to the LaraNode repository receives a question, it automatically loads the relevant skill based on the `description` field. The skill provides precise, contextual guidance specific to the matched package. ### Example Activations | User Question | Activated Skill | Guidance Provided | | -------------------------------------- | ----------------------------------- | --------------------------------------------------------- | | "How do I set up model relationships?" | `laranode-db` | Eloquent relationship patterns (hasMany, belongsTo, etc.) | | "How do I define API routes?" | `laranode-router` | Route decorators, groups, resource routing | | "Send email on user registration" | `laranode-events` + `laranode-mail` | Event listener + Mailable class patterns | | "Cache database queries" | `laranode-cache` + `laranode-db` | Cache::remember() with query builder | | "Authenticate API requests" | `laranode-auth` | JWT token generation + auth middleware | | "Generate a PDF report" | `laranode-pdf` | PDF generation with Puppeteer options | | "Parse user-uploaded CSV" | `laranode-csv` | CSV.parse(), import concerns, streaming | | "Monitor queue performance" | `laranode-horizon` | Dashboard setup, worker management | # Auth Package The `@lara-node/auth` package provides JWT authentication, password hashing, and token encryption. ## Installation ```bash pnpm add @lara-node/auth @lara-node/core bcrypt jsonwebtoken ``` ## Overview Features include: - **JWT token generation** and verification - **Password hashing** with bcrypt (scrypt fallback) - **Auth middleware** for protecting routes - **Token encryption** with AES-256-GCM ## Quick Start ```typescript import { generateToken, verifyToken, hashPassword, comparePassword } from "@lara-node/auth"; // Hash password const hashed = await hashPassword("secret"); // Verify password const isValid = await comparePassword("secret", hashed); // Generate token const token = generateToken({ userId: 1 }, 3600); // 1 hour // Verify token const payload = verifyToken(token); ``` ## Key Exports | Export | Description | | ------------------- | ------------------------------ | | `generateToken()` | Create JWT token | | `verifyToken()` | Verify and decode token | | `hashPassword()` | Hash password with bcrypt | | `comparePassword()` | Compare password with hash | | `authMiddleware` | Express auth middleware | | `encryptToken()` | Encrypt token with AES-256-GCM | | `decryptToken()` | Decrypt token | ## Next Steps - [Token Generation](https://laranode.doitrixtech.co.ke/packages/auth/tokens) -- JWT tokens - [Password Hashing](https://laranode.doitrixtech.co.ke/packages/auth/passwords) -- Hash passwords - [Auth Middleware](https://laranode.doitrixtech.co.ke/packages/auth/middleware) -- Protect routes # Token Encryption Encrypt and decrypt tokens using AES-256-GCM. ## Configuration Requires `APP_KEY` environment variable: ```dotenv APP_KEY=base64:your-32-byte-key-here ``` Generate a key: ```bash pnpm exec artisan key:generate ``` ## Encrypting Tokens ```typescript import { encryptToken } from "@lara-node/auth"; const encrypted = encryptToken(token); ``` ## Decrypting Tokens ```typescript import { decryptToken } from "@lara-node/auth"; const decrypted = decryptToken(encrypted); ``` ## Use Cases ### Password Reset Tokens ```typescript const token = generateToken({ userId: user.id, type: "reset" }, 3600); const encrypted = encryptToken(token); // Send encrypted token via email await Mail.to(user.email).send(new ResetPasswordMail(encrypted)); ``` ### Email Verification ```typescript const token = generateToken({ userId: user.id, email: user.email }, 86400); const encrypted = encryptToken(token); // Store in database or send via email ``` ## Next Steps - [Token Generation](https://laranode.doitrixtech.co.ke/packages/auth/tokens) -- JWT tokens - [Password Hashing](https://laranode.doitrixtech.co.ke/packages/auth/passwords) -- Hash passwords - [Auth Middleware](https://laranode.doitrixtech.co.ke/packages/auth/middleware) -- Protect routes # Auth Middleware Protect routes with JWT authentication middleware. ## Basic Usage ```typescript import { authMiddleware } from "@lara-node/auth"; Route.get("/profile", UserController.profile).middleware("auth"); ``` ## How It Works The middleware: 1. Extracts the `Bearer` token from `Authorization` header 2. Verifies the token 3. Sets `req.user` with the decoded payload 4. Returns 401 if token is invalid or missing ## Accessing User ```typescript @Route.get('/profile') async profile(req: Request) { // req.user contains the decoded token payload const userId = req.user.userId return User.find(userId) } ``` ## With User Loader Load the full user model: ```typescript // In middleware configuration { auth: { middleware: AuthMiddleware, options: { userLoader: async (payload) => { return User.find(payload.userId) } } } } ``` Then access the full user: ```typescript @Route.get('/profile') async profile(req: Request) { return req.user // Full User model } ``` ## Registering the Middleware ```typescript export class MiddlewareProvider extends MiddlewareServiceProvider { registerMiddleware() { return { aliases: { auth: AuthMiddleware, }, groups: { api: ["auth", "throttle"], }, }; } } ``` ## Next Steps - [Token Generation](https://laranode.doitrixtech.co.ke/packages/auth/tokens) -- JWT tokens - [Password Hashing](https://laranode.doitrixtech.co.ke/packages/auth/passwords) -- Hash passwords - [Built-in Middleware](https://laranode.doitrixtech.co.ke/packages/middlewares/built-in) -- All middleware # Password Hashing Securely hash and verify passwords using bcrypt. ## Hashing Passwords ```typescript import { hashPassword } from "@lara-node/auth"; const hashed = await hashPassword("my-secret-password"); ``` ## Verifying Passwords ```typescript import { comparePassword } from "@lara-node/auth"; const isValid = await comparePassword("my-secret-password", hashed); // true or false ``` ## Usage in Registration ```typescript @Route.post('/register') async register(req: Request) { const { name, email, password } = req.body const user = await User.create({ name, email, password: await hashPassword(password), }) return user.toJSON() } ``` ## Usage in Login ```typescript @Route.post('/login') async login(req: Request) { const { email, password } = req.body const user = await User.where('email', email).first() if (!user) { return { error: 'User not found' } } const isValid = await comparePassword(password, user.password) if (!isValid) { return { error: 'Invalid password' } } const token = generateToken({ userId: user.id }, 86400) return { token } } ``` ## Using with Observers Hash passwords automatically using a model observer: ```typescript @Observe(User) class UserObserver extends Observer { async creating(user: User) { user.password = await hashPassword(user.password); } async updating(user: User) { if (user.isDirty("password")) { user.password = await hashPassword(user.password); } } } ``` ## Next Steps - [Token Generation](https://laranode.doitrixtech.co.ke/packages/auth/tokens) -- JWT tokens - [Auth Middleware](https://laranode.doitrixtech.co.ke/packages/auth/middleware) -- Protect routes - [Token Encryption](https://laranode.doitrixtech.co.ke/packages/auth/encryption) -- Encrypt tokens # Token Generation Generate and verify JWT tokens for authentication. ## Generating Tokens ```typescript import { generateToken } from "@lara-node/auth"; // Basic token (expires in 1 hour) const token = generateToken({ userId: 1 }, 3600); // Custom payload const token = generateToken( { userId: 1, role: "admin", permissions: ["read", "write"], }, 86400, ); // 24 hours ``` ## Verifying Tokens ```typescript import { verifyToken } from "@lara-node/auth"; try { const payload = verifyToken(token); console.log(payload.userId); } catch (error) { // Token is invalid or expired } ``` ## Token Expiry Tokens expire based on the seconds provided: ```typescript generateToken(payload, 60); // 1 minute generateToken(payload, 3600); // 1 hour generateToken(payload, 86400); // 24 hours generateToken(payload, 604800); // 7 days ``` ## Usage in Auth Flow ```typescript @Route("/api/auth") class AuthController { @Route.post("/login") async login(req: Request) { const { email, password } = req.body; const user = await User.where("email", email).first(); if (!user || !(await comparePassword(password, user.password))) { return { error: "Invalid credentials" }; } const token = generateToken({ userId: user.id }, 86400); return { token, user: user.toJSON() }; } } ``` ## Next Steps - [Auth Middleware](https://laranode.doitrixtech.co.ke/packages/auth/middleware) -- Protect routes - [Password Hashing](https://laranode.doitrixtech.co.ke/packages/auth/passwords) -- Hash passwords - [Token Encryption](https://laranode.doitrixtech.co.ke/packages/auth/encryption) -- Encrypt tokens # Cache Package The `@lara-node/cache` package provides multi-driver caching with rate limiting support. ## Installation ```bash pnpm add @lara-node/cache @lara-node/core ``` ## Overview Features include: - **Multiple drivers** -- File, Database, Redis - **Rate limiting** -- Laravel-style rate limiter - **Cache prefix** management - **Remember pattern** for caching queries - **Telescope integration** ## Quick Start ```typescript import { Cache } from "@lara-node/cache"; // Store Cache.set("key", "value", 3600); // 1 hour // Get const value = Cache.get("key"); // Get with default const value = Cache.get("key", "default"); // Remember (get or set) const users = Cache.remember("users", 3600, async () => { return User.all(); }); // Delete Cache.del("key"); // Check exists Cache.has("key"); ``` ## Key Exports | Export | Description | | -------------- | -------------------- | | `Cache` | Cache facade | | `CacheManager` | Cache driver manager | | `RateLimiter` | Rate limiter facade | | `FileCache` | File-based cache | | `DBCache` | Database cache | | `RedisCache` | Redis cache | ## Configuration ```typescript // config/cache.config.ts export default { driver: process.env.CACHE_DRIVER || "file", prefix: process.env.CACHE_PREFIX || "vest_", path: process.env.CACHE_PATH || "./storage/cache", redis: { host: process.env.REDIS_HOST || "127.0.0.1", port: parseInt(process.env.REDIS_PORT || "6379"), password: process.env.REDIS_PASSWORD, }, }; ``` ## Next Steps - [Cache Drivers](https://laranode.doitrixtech.co.ke/packages/cache/drivers) -- File, DB, Redis - [Rate Limiting](https://laranode.doitrixtech.co.ke/packages/cache/rate-limiting) -- Rate limiter # Cache Drivers LaraNode supports multiple cache drivers. ## Configuration Set the driver via `CACHE_DRIVER` environment variable: ```dotenv CACHE_DRIVER=file ``` Available drivers: `file`, `database`, `redis` ## File Cache Stores cache in files on disk: ```dotenv CACHE_DRIVER=file CACHE_PATH=./storage/cache ``` ```typescript Cache.set("key", "value", 3600); Cache.get("key"); ``` ## Database Cache Stores cache in a database table: ```dotenv CACHE_DRIVER=database ``` ```typescript Cache.set("key", "value", 3600); Cache.get("key"); ``` ## Redis Cache Stores cache in Redis: ```dotenv CACHE_DRIVER=redis REDIS_HOST=127.0.0.1 REDIS_PORT=6379 ``` ```typescript Cache.set("key", "value", 3600); Cache.get("key"); ``` ## Cache Methods ```typescript // Store Cache.set("key", value, ttl); // Get Cache.get("key"); Cache.get("key", "default"); // Delete Cache.del("key"); Cache.forget("key"); // Check Cache.has("key"); // Clear all Cache.clear(); Cache.flush(); // Keys Cache.keys(); // Remember Cache.remember("key", ttl, async () => { return expensiveOperation(); }); ``` ## Prefix Management ```typescript import { generateCacheKey, cacheDelPrefix } from "@lara-node/cache"; // Generate prefixed key const key = generateCacheKey("users", userId); // Delete all keys with prefix await cacheDelPrefix("users:"); ``` ## Next Steps - [Cache Overview](https://laranode.doitrixtech.co.ke/packages/cache) -- Cache overview - [Rate Limiting](https://laranode.doitrixtech.co.ke/packages/cache/rate-limiting) -- Rate limiter # Rate Limiting LaraNode provides a Laravel-style rate limiter for controlling request frequency. ## Basic Usage ```typescript import { RateLimiter } from "@lara-node/cache"; const key = `api:${req.ip}`; if (RateLimiter.tooManyAttempts(key, 60)) { return res.status(429).json({ error: "Too many requests" }); } RateLimiter.hit(key, 60); ``` ## RateLimiter Methods ```typescript // Check if too many attempts RateLimiter.tooManyAttempts(key, maxAttempts, decaySeconds); // Record an attempt RateLimiter.hit(key, decaySeconds); // Get attempt count RateLimiter.attempts(key); // Clear attempts RateLimiter.clear(key); // Attempt and check in one call const allowed = RateLimiter.attempt(key, maxAttempts, decaySeconds); ``` ## Named Limiters Define reusable limiters: ```typescript import { defineRateLimiter, getNamedLimiter } from "@lara-node/cache"; defineRateLimiter("api", { maxAttempts: 60, decaySeconds: 60 }); defineRateLimiter("login", { maxAttempts: 5, decaySeconds: 300 }); // Usage const limiter = getNamedLimiter("api"); if (limiter.tooManyAttempts(key)) { // Rate limited } ``` ## In Middleware ```typescript import { RateLimitExceededException } from "@lara-node/cache"; export class ThrottleMiddleware { async handle(req, res, next) { const key = `api:${req.ip}`; if (!RateLimiter.attempt(key, 60, 60)) { throw new RateLimitExceededException(); } next(); } } ``` ## Route-Level Throttling ```typescript Route.get("/api/data", DataController.index).middleware("throttle:60,1"); // 60 per minute ``` ## Next Steps - [Cache Drivers](https://laranode.doitrixtech.co.ke/packages/cache/drivers) -- Cache drivers - [Cache Overview](https://laranode.doitrixtech.co.ke/packages/cache) -- Cache overview - [Middleware](https://laranode.doitrixtech.co.ke/packages/middlewares) -- Built-in middleware # Carbon Package The `@lara-node/carbon` package provides a Laravel Carbon-inspired date/time library. Zero dependencies, immutable, and fluent. ## Installation ```bash pnpm add @lara-node/carbon ``` ## Overview Features include: - **Immutable dates** by default - **Fluent API** for manipulation - **Human-readable diffs** - **Date intervals and periods** - **Zero dependencies** ## Quick Start ```typescript import { Carbon } from "@lara-node/carbon"; // Current time const now = Carbon.current(); // Create from string const date = Carbon.from("2024-01-15"); // Create from timestamp const date = Carbon.fromTimestamp(1705276800); // Format date.format("YYYY-MM-DD HH:mm:ss"); // Human readable date.diffForHumans(); // "2 days ago" ``` ## Key Exports | Export | Description | | ------------------- | -------------------------- | | `Carbon` | Main date class | | `CarbonImmutable` | Strictly immutable variant | | `CarbonInterval` | Duration representation | | `CarbonPeriod` | Iterable date range | | `MONDAY` - `SUNDAY` | Day constants | ## Next Steps - [Creating Dates](https://laranode.doitrixtech.co.ke/packages/carbon/creating) -- Create dates - [Manipulation](https://laranode.doitrixtech.co.ke/packages/carbon/manipulation) -- Modify dates - [Formatting](https://laranode.doitrixtech.co.ke/packages/carbon/formatting) -- Format dates # Date Comparison Compare dates with Carbon's comparison methods. ## Basic Comparisons ```typescript date.isPast(); // Before now date.isFuture(); // After now date.isToday(); // Is today date.isTomorrow(); // Is tomorrow date.isYesterday(); // Is yesterday ``` ## Day Checks ```typescript date.isWeekend(); // Saturday or Sunday date.isWeekday(); // Monday-Friday date.isMonday(); date.isTuesday(); // ... all days ``` ## Year Checks ```typescript date.isLeapYear(); // Leap year date.isDST(); // Daylight saving time ``` ## Between ```typescript date.isBetween(start, end); // Between two dates ``` ## Diffs ```typescript date.diffInDays(other); // Days difference date.diffInHours(other); // Hours difference date.diffInMinutes(other); // Minutes difference date.diffInSeconds(other); // Seconds difference date.diffInMonths(other); // Months difference date.diffInYears(other); // Years difference ``` ## Next Steps - [Formatting](https://laranode.doitrixtech.co.ke/packages/carbon/formatting) -- Format dates - [Intervals](https://laranode.doitrixtech.co.ke/packages/carbon/intervals) -- Intervals and periods - [Carbon Overview](https://laranode.doitrixtech.co.ke/packages/carbon) -- Overview # Creating Dates Create Carbon date instances in various ways. ## Current Time ```typescript import { Carbon } from "@lara-node/carbon"; const now = Carbon.current(); const today = Carbon.today(); const tomorrow = Carbon.tomorrow(); const yesterday = Carbon.yesterday(); ``` ## From String ```typescript const date = Carbon.from("2024-01-15"); const date = Carbon.from("2024-01-15 10:30:00"); ``` ## From Timestamp ```typescript const date = Carbon.fromTimestamp(1705276800); ``` ## From Format ```typescript const date = Carbon.fromFormat("2024-01-15", "YYYY-MM-DD"); ``` ## Min/Max ```typescript const earliest = Carbon.min(date1, date2, date3); const latest = Carbon.max(date1, date2, date3); ``` ## Next Steps - [Carbon Overview](https://laranode.doitrixtech.co.ke/packages/carbon) -- Overview - [Manipulation](https://laranode.doitrixtech.co.ke/packages/carbon/manipulation) -- Modify dates - [Formatting](https://laranode.doitrixtech.co.ke/packages/carbon/formatting) -- Format dates # Date Formatting Format Carbon dates with various tokens. ## Format Tokens ```typescript const date = Carbon.from("2024-01-15 14:30:00"); date.format("YYYY"); // 2024 date.format("MM"); // 01 date.format("DD"); // 15 date.format("HH"); // 14 date.format("mm"); // 30 date.format("ss"); // 00 date.format("YYYY-MM-DD"); // 2024-01-15 date.format("HH:mm:ss"); // 14:30:00 ``` ## Common Formats ```typescript date.format("YYYY-MM-DD HH:mm:ss"); // 2024-01-15 14:30:00 date.format("MM/DD/YYYY"); // 01/15/2024 date.format("DD MMM YYYY"); // 15 Jan 2024 date.format("dddd, MMMM Do YYYY"); // Monday, January 15th 2024 ``` ## Human Readable ```typescript const past = Carbon.from("2024-01-10"); const future = Carbon.from("2024-01-20"); past.diffForHumans(); // "5 days ago" future.diffForHumans(); // "5 days from now" ``` ## Next Steps - [Manipulation](https://laranode.doitrixtech.co.ke/packages/carbon/manipulation) -- Modify dates - [Comparison](https://laranode.doitrixtech.co.ke/packages/carbon/comparison) -- Compare dates - [Intervals](https://laranode.doitrixtech.co.ke/packages/carbon/intervals) -- Intervals and periods # Intervals & Periods Work with date intervals and periods. ## CarbonInterval Represent a duration: ```typescript import { CarbonInterval } from "@lara-node/carbon"; const interval = CarbonInterval.years(2).months(3).days(5); interval.years; // 2 interval.months; // 3 interval.days; // 5 // Between two dates const interval = CarbonInterval.between(date1, date2); // Human readable interval.humanize(); // "2 years 3 months 5 days" ``` ## CarbonPeriod Iterate over a date range: ```typescript import { CarbonPeriod } from "@lara-node/carbon"; const period = CarbonPeriod.create(startDate, endDate); // Iterate for (const date of period.everyDay()) { console.log(date.format("YYYY-MM-DD")); } // Frequencies period.everyDay(); period.everyWeek(); period.everyMonth(); period.everyYear(); ``` ## Next Steps - [Comparison](https://laranode.doitrixtech.co.ke/packages/carbon/comparison) -- Compare dates - [Formatting](https://laranode.doitrixtech.co.ke/packages/carbon/formatting) -- Format dates - [Carbon Overview](https://laranode.doitrixtech.co.ke/packages/carbon) -- Overview # Date Manipulation Modify dates with Carbon's fluent API. ## Accessors ```typescript const date = Carbon.current(); date.year; // 2024 date.month; // 1-12 date.day; // 1-31 date.hour; // 0-23 date.minute; // 0-59 date.second; // 0-59 date.timestamp; // Unix timestamp date.quarter; // 1-4 date.weekOfYear; // 1-52 date.dayOfYear; // 1-365 date.decade; // Decade number date.age; // Age in years date.monthName; // January date.dayName; // Monday ``` ## Start/End of ```typescript date.startOfDay(); date.endOfDay(); date.startOfWeek(); date.endOfWeek(); date.startOfMonth(); date.endOfMonth(); date.startOfQuarter(); date.endOfQuarter(); date.startOfYear(); date.endOfYear(); date.startOfDecade(); date.endOfDecade(); date.startOfCentury(); ``` ## Add/Subtract ```typescript date.addDays(5); date.addMonths(2); date.addYears(1); date.addHours(3); date.addMinutes(30); date.addSeconds(60); date.subDays(5); date.subMonths(2); date.subYears(1); ``` ## Navigation ```typescript date.next(); // Next day date.previous(); // Previous day date.nextWeekday(); // Next weekday date.nthOfMonth(2); // 2nd occurrence of day in month ``` ## Next Steps - [Creating Dates](https://laranode.doitrixtech.co.ke/packages/carbon/creating) -- Create dates - [Formatting](https://laranode.doitrixtech.co.ke/packages/carbon/formatting) -- Format dates - [Comparison](https://laranode.doitrixtech.co.ke/packages/carbon/comparison) -- Compare dates # Console Package The `@lara-node/console` package provides a Laravel Artisan-style CLI with commands and scheduler. ## Installation ```bash pnpm add @lara-node/console @lara-node/core ``` ## Overview Features include: - **40+ built-in commands** for migrations, queues, cache, routes - **Custom command** creation - **Interactive prompts** for input - **Colored output** with tables - **Scheduler integration** ## The `artisan` Binary ```bash pnpm exec artisan [command] ``` ## Key Exports | Export | Description | | --------- | ------------------ | | `Command` | Base command class | | `Kernel` | Command kernel | ## Next Steps - [Writing Commands](https://laranode.doitrixtech.co.ke/packages/console/commands) -- Create commands - [Built-in Commands](https://laranode.doitrixtech.co.ke/packages/console/built-in) -- All commands # Built-in Commands LaraNode includes 40+ Artisan-style commands. ## Server ```bash pnpm exec artisan serve # Start HTTP server ``` ## Migrations ```bash pnpm exec artisan migrate # Run migrations pnpm exec artisan migrate:fresh # Drop and re-run pnpm exec artisan migrate:rollback # Rollback last batch pnpm exec artisan migrate:status # Show status pnpm exec artisan make:migration # Create migration ``` ## Database ```bash pnpm exec artisan db:seed # Run seeders pnpm exec artisan make:seeder # Create seeder pnpm exec artisan db:wipe # Drop all tables ``` ## Cache ```bash pnpm exec artisan cache:clear # Clear cache pnpm exec artisan cache:forget # Forget key ``` ## Queue ```bash pnpm exec artisan queue:work # Start worker pnpm exec artisan queue:restart # Restart workers pnpm exec artisan queue:flush-failed # Flush failed jobs ``` ## Horizon ```bash pnpm exec artisan horizon # Start Horizon pnpm exec artisan horizon:pause # Pause workers pnpm exec artisan horizon:resume # Resume workers pnpm exec artisan horizon:stop # Stop Horizon pnpm exec artisan horizon:status # Show status ``` ## Events ```bash pnpm exec artisan event:cache # Cache events pnpm exec artisan event:clear # Clear event cache pnpm exec artisan make:event # Create event pnpm exec artisan make:listener # Create listener pnpm exec artisan make:subscriber # Create subscriber ``` ## Routes ```bash pnpm exec artisan route:list # List all routes ``` ## Mail ```bash pnpm exec artisan broadcast:auth # Broadcast auth endpoint ``` ## Utilities ```bash pnpm exec artisan key:generate # Generate APP_KEY pnpm exec artisan vendor:publish # Publish vendor files pnpm exec artisan docs:generate # Generate documentation ``` ## Next Steps - [Writing Commands](https://laranode.doitrixtech.co.ke/packages/console/commands) -- Create commands - [Console Overview](https://laranode.doitrixtech.co.ke/packages/console) -- Overview - [Scheduler](https://laranode.doitrixtech.co.ke/packages/queue/scheduler) -- Task scheduler # Writing Commands Create custom Artisan-style commands. ## Creating a Command Extend the `Command` class: ```typescript import { Command } from "@lara-node/console"; class GreetCommand extends Command { signature = "greet {name}"; description = "Greet someone"; async handle(args: Record) { const name = this.argument("name"); this.info(`Hello, ${name}!`); } } ``` ## Command Properties ```typescript class MyCommand extends Command { signature = "my:command {arg1} {--option=}"; description = "Command description"; arguments = ["arg1"]; options = ["--option"]; } ``` ## Output Methods ```typescript this.info("Information message"); this.error("Error message"); this.warn("Warning message"); this.success("Success message"); this.fail("Failure message"); ``` ## Tables ```typescript this.table( ["Name", "Email", "Role"], [ ["John", "john@example.com", "Admin"], ["Jane", "jane@example.com", "User"], ], ); ``` ## Interactive Input ```typescript const name = await this.ask("What is your name?"); const confirmed = await this.confirm("Are you sure?"); const choice = await this.choice("Choose an option", ["A", "B", "C"]); ``` ## Next Steps - [Built-in Commands](https://laranode.doitrixtech.co.ke/packages/console/built-in) -- All commands - [Console Overview](https://laranode.doitrixtech.co.ke/packages/console) -- Overview # Core Package The `@lara-node/core` package is the foundation of the LaraNode framework. It provides the IoC container, Application bootstrap, Service Provider system, and configuration management. ## Installation ```bash pnpm add @lara-node/core express cors reflect-metadata ``` ## Overview Core provides: - **IoC Container** -- Dependency injection with automatic resolution - **Application** -- Express wrapper with lifecycle management - **Service Providers** -- Modular service registration - **Middleware Stack** -- Laravel-style middleware management - **Configuration** -- Dot-notation config system ## Quick Start ```typescript import { Application, Container } from "@lara-node/core"; import "reflect-metadata"; const container = new Container(); const app = new Application(container); app.register(AppServiceProvider); await app.boot(); await app.listen(3000); ``` ## Key Exports | Export | Description | | --------------------------- | -------------------------------- | | `Container` | IoC container class | | `container` | Singleton container instance | | `Application` | Application bootstrap class | | `ServiceProvider` | Base class for service providers | | `MiddlewareServiceProvider` | Base for middleware providers | | `MiddlewareStack` | Middleware manager | | `FormRequest` | Validated request base class | | `Injectable()` | DI decorator | | `@Provider()` | Auto-discovery decorator | | `config()` | Config helper | | `setConfig()` | Set config value | | `hasConfig()` | Check config exists | | `allConfig()` | Get all config | ## Architecture ```text ┌─────────────────────────────────────┐ │ Application │ │ ┌─────────────┐ ┌──────────────┐ │ │ │ Container │ │ Middleware │ │ │ │ (IoC) │ │ Stack │ │ │ └─────────────┘ └──────────────┘ │ │ ┌───────────────────────────────┐ │ │ │ Service Providers │ │ │ └───────────────────────────────┘ │ │ ┌───────────────────────────────┐ │ │ │ Configuration System │ │ │ └───────────────────────────────┘ │ └─────────────────────────────────────┘ ``` ## Next Steps - [Container](https://laranode.doitrixtech.co.ke/packages/core/container) -- Deep dive into the IoC container - [Application](https://laranode.doitrixtech.co.ke/packages/core/application) -- Application bootstrap - [Service Providers](https://laranode.doitrixtech.co.ke/packages/core/service-providers) -- Creating providers - [Configuration](https://laranode.doitrixtech.co.ke/packages/core/config) -- Configuration system - [Request](https://laranode.doitrixtech.co.ke/packages/core/request) -- Form request validation # Application The `Application` class is the main bootstrap class for LaraNode applications. It wraps Express and manages the service provider lifecycle. ## Creating an Application ```typescript import { Application, Container } from "@lara-node/core"; const container = new Container(); const app = new Application(container); ``` ## Registering Providers ```typescript app.register(AppServiceProvider); app.register(RouteServiceProvider); app.register(CacheServiceProvider); ``` ## Booting ```typescript await app.boot(); ``` This triggers: 1. `register()` on all providers 2. `boot()` on all providers 3. Middleware registration 4. Express app setup ## Starting the Server ```typescript // Default port (from config or 3000) await app.listen(); // Specific port await app.listen(3000); // With callback await app.listen(3000, () => { console.log("Server started"); }); ``` ## Accessing Express ```typescript const expressApp = app.getExpress(); expressApp.use(cors()); ``` ## Lifecycle Hooks ```typescript // Before booting app.booting(() => { console.log("Booting..."); }); // After booting app.booted(() => { console.log("Booted!"); }); ``` ## Global Middleware ```typescript app.useGlobalMiddleware(CorsMiddleware); app.useGlobalMiddleware(RequestLoggerMiddleware); ``` ## Complete Example ```typescript import { Application, Container } from "@lara-node/core"; import { AppServiceProvider } from "./app/Providers/AppServiceProvider"; import { RouteServiceProvider } from "./app/Providers/RouteServiceProvider"; import "reflect-metadata"; const container = new Container(); const app = new Application(container); // Register providers app.register(AppServiceProvider); app.register(RouteServiceProvider); // Lifecycle hooks app.booting(() => { console.log("Starting application..."); }); app.booted(() => { console.log("Application booted!"); }); // Boot and start async function bootstrap() { await app.boot(); await app.listen(3000); console.log("Server running on http://localhost:3000"); } bootstrap(); ``` ## Next Steps - [Container](https://laranode.doitrixtech.co.ke/packages/core/container) -- IoC container - [Service Providers](https://laranode.doitrixtech.co.ke/packages/core/service-providers) -- Creating providers - [Getting Started](https://laranode.doitrixtech.co.ke/guide/getting-started) -- Quick start guide # Configuration (Core) LaraNode provides a dot-notation configuration system for managing application settings. ## Config Functions ### `config()` Get a configuration value: ```typescript import { config } from "@lara-node/core"; // Get value const host = config("database.mysql.host"); // Get with default const port = config("app.port", 3000); // Get entire section const dbConfig = config("database"); ``` ### `setConfig()` Set a configuration value: ```typescript import { setConfig } from "@lara-node/core"; setConfig("app.debug", true); setConfig("database.mysql.host", "localhost"); ``` ### `hasConfig()` Check if a configuration key exists: ```typescript import { hasConfig } from "@lara-node/core"; if (hasConfig("cache.redis")) { // Redis is configured } ``` ### `allConfig()` Get all configuration: ```typescript import { allConfig } from "@lara-node/core"; const all = allConfig(); console.log(all.database); ``` ## Loading Configuration Configuration is typically loaded from files in `src/config/`: ```typescript // src/bootstrap/app.ts import { setConfig } from "@lara-node/core"; import databaseConfig from "../config/database.config"; import cacheConfig from "../config/cache.config"; setConfig("database", databaseConfig); setConfig("cache", cacheConfig); ``` ## Dot Notation Access nested values with dot notation: ```typescript // config/database.config.ts export default { mysql: { host: "localhost", port: 3306, }, }; // Access config("database.mysql.host"); // 'localhost' config("database.mysql.port"); // 3306 ``` ## Next Steps - [Configuration Guide](https://laranode.doitrixtech.co.ke/guide/configuration) -- Full configuration guide - [Application](https://laranode.doitrixtech.co.ke/packages/core/application) -- Application class - [Service Providers](https://laranode.doitrixtech.co.ke/packages/core/service-providers) -- Service providers # IoC Container The Container is LaraNode's dependency injection container. It manages class dependencies and performs automatic resolution. ## Basic Usage ```typescript import { Container, container } from "@lara-node/core"; // Use the singleton container.bind("service", () => new MyService()); const service = container.make("service"); // Or create your own const myContainer = new Container(); ``` ## Binding Methods ### `bind()` Register a transient binding (new instance each time): ```typescript container.bind(Logger, () => new Logger()); container.bind("mailer", () => new MailManager()); ``` ### `singleton()` Register a singleton (same instance always): ```typescript container.singleton(DatabaseService, () => { return new DatabaseService(config("database")); }); ``` ### `instance()` Bind an existing instance: ```typescript const expressApp = express(); container.instance("express", expressApp); ``` ### `alias()` Create an alias for an existing binding: ```typescript container.bind("cache", () => new CacheManager()); container.alias("cache", CacheManager); ``` ## Resolving ### `make()` Resolve a service from the container: ```typescript const service = container.make(UserService); const mailer = container.make("mailer"); ``` ### `app()` Helper Convenient helper for resolving: ```typescript import { app } from "@lara-node/core"; // Resolve a service const service = app(UserService); // Get the container const container = app(); ``` ## Automatic Resolution The container can automatically resolve dependencies: ```typescript import { Injectable } from "@lara-node/core"; @Injectable() class UserService { constructor( private db: DatabaseService, private cache: CacheManager, ) {} } // Container resolves DatabaseService and CacheManager automatically const service = container.make(UserService); ``` ## Resolving Callbacks Register callbacks that fire when a type is resolved: ```typescript container.resolving(UserService, (service) => { service.initialize(); }); container.resolving("*", (resolved) => { console.log("Resolved:", resolved.constructor.name); }); ``` ## Checking Bindings ```typescript // Check if bound container.bound("service"); // boolean // Check if singleton container.isSingleton(Logger); // boolean ``` ## Practical Example ```typescript // Register services container.singleton(DatabaseService, () => { return new DatabaseService(config("database")); }); container.singleton(CacheManager, () => { return new CacheManager(config("cache")); }); container.bind(UserService, () => { return new UserService(container.make(DatabaseService), container.make(CacheManager)); }); // Resolve const users = container.make(UserService); const allUsers = await users.all(); ``` ## Next Steps - [Application](https://laranode.doitrixtech.co.ke/packages/core/application) -- Application bootstrap - [Service Providers](https://laranode.doitrixtech.co.ke/packages/core/service-providers) -- Creating providers - [Dependency Injection](https://laranode.doitrixtech.co.ke/guide/dependency-injection) -- DI guide # Form Request The `FormRequest` is a Laravel-inspired request validation class that encapsulates validation logic, authorization, and input handling into a single, type-safe class. When type-hinted in a controller method, the router automatically instantiates it, runs validation, and injects it — returning a `422` response on failure. ## Overview A `FormRequest` subclass declares its validation rules in a `rules()` method. The router detects it via TypeScript decorator metadata, creates an instance with the incoming Express request, and calls `validate()` before the controller runs. ## Creating a FormRequest ```typescript import { FormRequest } from "@lara-node/core"; class StoreUserRequest extends FormRequest { rules() { return { name: "required|string|max:255", email: "required|email|unique:users,email", password: "required|string|min:8|confirmed", }; } } ``` ## Usage in Controller Type-hint the request class in your controller method. The router resolves, validates, and injects it automatically: ```typescript import { Controller, Route } from "@lara-node/router"; @Route("/api/users") export class UserController { @Route.post("/") async store(req: StoreUserRequest, res: Response) { // Validation already passed — req is a StoreUserRequest instance const data = req.validated(); // data is typed as Record } } ``` Or with the tuple syntax: ```typescript router.post("/api/users", [UserController, "store"]); ``` ## Available Methods | Method | Description | | ----------------------- | ---------------------------------------------- | | `validated()` | Returns the validated data (`T`) | | `safe()` | Returns a shallow copy of validated data | | `errors()` | Returns field-keyed error messages | | `fails()` | `true` if validation failed | | `passed()` | `true` if validation passed | | `validate()` | Runs validation manually | | `input(key?, default?)` | Get input from body, query, or params | | `only(...keys)` | Get only the specified input fields | | `except(...keys)` | Get all input except the specified fields | | `bearerToken()` | Extract Bearer token from Authorization header | ### Request Passthrough The FormRequest exposes the most common Express request properties directly: ```typescript req.body // Request body req.query // Query parameters req.params // Route parameters req.headers // Request headers req.ip // Client IP req.method // HTTP method req.path // Request path req.url // Request URL req.originalUrl // Original URL ``` Access the underlying Express request via `getRequest()`: ```typescript const expressReq = req.getRequest(); ``` ## Authorization Override `authorize()` to control access. Returning `false` throws a `ValidationError` with a `422` response: ```typescript class UpdateUserRequest extends FormRequest { authorize(): boolean { const user = this.getRequest().user; return user?.id === this.params.id; } rules() { return { name: "string|max:255", email: "email|unique:users,email", }; } } ``` ## Custom Error Messages Override `messages()` to customize validation error messages: ```typescript class LoginRequest extends FormRequest { rules() { return { email: "required|email", password: "required|min:8", }; } messages(): Record { return { "email.required": "An email address is required.", "email.email": "Provide a valid email address.", "password.min": "Password must be at least 8 characters.", }; } } ``` ## Generic Return Type The `FormRequest` generic lets you type the return of `validated()`: ```typescript interface CreateUserData { name: string; email: string; password: string; } class StoreUserRequest extends FormRequest { rules() { return { name: "required|string|max:255", email: "required|email", password: "required|string|min:8", }; } } // In controller: const data = req.validated(); // type: CreateUserData data.name; // string data.email; // string ``` ## Manual Validation You can also instantiate and validate a FormRequest manually outside the router: ```typescript const expressReq = getFromSomewhere(); const request = new LoginRequest(expressReq); try { await request.validate(); const data = request.validated(); } catch (error) { console.error(request.errors()); } ``` ## How Auto-Detection Works The router uses `Reflect.getMetadata("design:paramtypes")` (emitted by TypeScript's `emitDecoratorMetadata`) to inspect controller method parameter types at runtime. When the first parameter extends `FormRequest`, the router: 1. Instantiates the class with the Express `req` 2. Calls `validate()` automatically 3. On success, passes the FormRequest instance to the controller 4. On failure, returns a `422` JSON response with error messages This requires `experimentalDecorators` and `emitDecoratorMetadata` in your `tsconfig.json` (both are already set by default). ## Next Steps - [Validator Package](https://laranode.doitrixtech.co.ke/packages/validator) -- Validation engine and rule reference - [Router Controllers](https://laranode.doitrixtech.co.ke/packages/router/controllers) -- Controller usage - [Validation Rules](https://laranode.doitrixtech.co.ke/packages/validator/rules) -- All available validation rules # Service Providers (Core) Service providers are the central place for configuring your LaraNode application. ## Overview Service providers handle: - Registering services in the IoC container - Booting services after registration - Registering middleware - Accessing configuration ## The ServiceProvider Class ```typescript import { ServiceProvider } from "@lara-node/core"; export class AppServiceProvider extends ServiceProvider { register() { // Bind services } boot() { // Boot services } } ``` ## The `app` Property Access the container via `this.app`: ```typescript export class AppServiceProvider extends ServiceProvider { register() { this.app.singleton(UserService, () => { return new UserService(this.app.make(DatabaseService)); }); } boot() { const service = this.app.make(UserService); service.initialize(); } } ``` ## Middleware Helpers ### `registerMiddleware()` Register middleware aliases: ```typescript export class HttpMiddlewareProvider extends MiddlewareServiceProvider { registerMiddleware() { return { aliases: { auth: AuthMiddleware, throttle: ThrottleMiddleware, }, groups: { api: ["auth", "throttle"], }, priority: ["throttle"], }; } } ``` ## Configuration Access Access config in providers: ```typescript boot() { const dbConfig = this.app.config('database') const cacheDriver = this.app.config('cache.driver') } ``` ## Lifecycle ```text 1. app.register(Provider) └─ Provider.register() 2. app.boot() ├─ Provider.booting() callbacks ├─ Provider.boot() └─ Provider.booted() callbacks ``` ## Next Steps - [Application](https://laranode.doitrixtech.co.ke/packages/core/application) -- Application class - [Container](https://laranode.doitrixtech.co.ke/packages/core/container) -- IoC container - [Guide: Service Providers](https://laranode.doitrixtech.co.ke/guide/service-providers) -- Full guide # Database Package The `@lara-node/db` package provides a Laravel Eloquent-inspired ORM with support for MySQL and MongoDB, migrations, seeders, traits, and observers. ## Installation ```bash pnpm add @lara-node/db mysql2 mongodb ``` ## Overview Features include: - **Model-based ORM** with Eloquent-like syntax - **MySQL and MongoDB** support - **Relationships** -- hasOne, hasMany, belongsTo, belongsToMany, and more - **Eager loading** with `with()` - **Query builder** with fluent API - **Migrations** and **seeders** - **Traits** -- SoftDeletes, Timestamps, Sluggable, Sortable, Searchable, Cacheable - **Observers** for model events - **Accessors and Mutators** - **DB facade** for raw queries ## Quick Start ### Define a Model ```typescript import { Model, use } from "@lara-node/db"; import { SoftDeletes, Timestamps } from "@lara-node/db"; @use(SoftDeletes, Timestamps) class User extends Model { static table = "users"; static fillable = ["name", "email", "password"]; static hidden = ["password"]; static casts = { email_verified_at: "date", }; } ``` ### Query Models ```typescript // Find by ID const user = await User.find(1); // Query builder const users = await User.where("active", true).orderBy("name").paginate(15); // Create const user = await User.create({ name: "John", email: "john@example.com", }); // Update await user.update({ name: "Jane" }); // Delete await user.delete(); ``` ## Key Exports | Export | Description | | -------------------- | ------------------------------- | | `Model` | Abstract base model class | | `EloquentBuilder` | Fluent query builder | | `DB` | Database facade for raw queries | | `Schema` | Schema builder for migrations | | `Observer` | Base class for model observers | | `@Observe()` | Decorator to wire observers | | `@use()` | Decorator to apply traits | | `SoftDeletes` | Soft delete trait | | `Timestamps` | Timestamps trait | | `Sluggable` | Slug generation trait | | `Sortable` | Sorting trait | | `Searchable` | Search trait | | `Cacheable` | Caching trait | ## Configuration ```typescript // config/database.config.ts export default { connection: process.env.DB_CONNECTION || "mysql", mysql: { host: process.env.DB_HOST || "127.0.0.1", port: parseInt(process.env.DB_PORT || "3306"), user: process.env.DB_USER || "root", password: process.env.DB_PASSWORD || "", database: process.env.DB_NAME || "vest", poolLimit: parseInt(process.env.DB_POOL_LIMIT || "10"), }, mongodb: { uri: process.env.MONGO_URI || "mongodb://localhost:27017", replicaSet: process.env.MONGO_REPLICA_SET, }, }; ``` ## Initialize Database ```typescript import { initDatabase } from "@lara-node/db"; await initDatabase(); ``` ## Next Steps - [Models](https://laranode.doitrixtech.co.ke/packages/db/models) -- Working with models - [Query Builder](https://laranode.doitrixtech.co.ke/packages/db/query-builder) -- Fluent queries - [Relationships](https://laranode.doitrixtech.co.ke/packages/db/relationships) -- Model relationships - [Migrations](https://laranode.doitrixtech.co.ke/packages/db/migrations) -- Database migrations # DB Facade The `DB` facade provides a fluent query builder for raw database operations without models. ## Basic Usage ```typescript import { DB } from "@lara-node/db"; // Select const users = await DB.select("SELECT * FROM users WHERE active = ?", [true]); // Table query const users = await DB.table("users").where("active", true).get(); ``` ## Query Builder ```typescript // Get all const users = await DB.table("users").get(); // Where const users = await DB.table("users").where("active", true).get(); // First const user = await DB.table("users").where("id", 1).first(); // Insert await DB.table("users").insert({ name: "John", email: "john@example.com", }); // Update await DB.table("users").where("id", 1).update({ name: "Jane", }); // Delete await DB.table("users").where("id", 1).delete(); ``` ## Raw Queries ```typescript // Select const users = await DB.select("SELECT * FROM users"); const users = await DB.select("SELECT * FROM users WHERE id = ?", [1]); // Insert await DB.insert("INSERT INTO users (name) VALUES (?)", ["John"]); // Update await DB.update("UPDATE users SET name = ? WHERE id = ?", ["Jane", 1]); // Delete await DB.delete("DELETE FROM users WHERE id = ?", [1]); // Statement await DB.statement("TRUNCATE TABLE users"); ``` ## Collections ```typescript const collection = DB.collection("users"); // MongoDB // MongoDB operations await collection.insertOne({ name: "John" }); await collection.findOne({ name: "John" }); await collection.updateOne({ name: "John" }, { $set: { age: 30 } }); await collection.deleteOne({ name: "John" }); ``` ## Transactions ```typescript // Using callback await DB.transaction(async (db) => { await db.table("users").insert({ name: "John" }); await db.table("profiles").insert({ user_id: 1 }); }); // Manual await DB.beginTransaction(); try { await DB.table("users").insert({ name: "John" }); await DB.commit(); } catch (error) { await DB.rollback(); throw error; } ``` ## Connection Management ```typescript import { initDatabase, closeDatabase, getPool } from "@lara-node/db"; // Initialize await initDatabase(); // Get connection pool const pool = getPool(); // Close await closeDatabase(); ``` ## Next Steps - [Models](https://laranode.doitrixtech.co.ke/packages/db/models) -- Working with models - [Query Builder](https://laranode.doitrixtech.co.ke/packages/db/query-builder) -- Eloquent query builder - [Migrations](https://laranode.doitrixtech.co.ke/packages/db/migrations) -- Database migrations # Migrations Migrations are version control for your database, allowing you to define and share your database schema. ## Creating Migrations ```bash pnpm exec artisan make:migration create_users_table ``` This creates a file in `src/database/migrations/`: ```typescript // 2024_01_01_000000_create_users_table.ts import { Schema, TableBuilder } from "@lara-node/db"; export async function up(schema: Schema) { schema.create("users", (table: TableBuilder) => { table.increments("id").primary(); table.string("name"); table.string("email").unique(); table.string("password"); table.timestamp("email_verified_at").nullable(); table.timestamps(); table.softDeletes(); }); } export async function down(schema: Schema) { schema.drop("users"); } ``` ## Running Migrations ```bash # Run all pending migrations pnpm exec artisan migrate # Rollback last batch pnpm exec artisan migrate:rollback # Rollback all and re-run pnpm exec artisan migrate:fresh # Check migration status pnpm exec artisan migrate:status ``` ## Schema Builder ### Creating Tables ```typescript schema.create("users", (table) => { table.increments("id").primary(); table.string("name"); table.timestamps(); }); ``` ### Column Types ```typescript table.increments("id"); // Auto-incrementing integer table.string("name"); // VARCHAR equivalent table.text("bio"); // TEXT table.integer("age"); // INTEGER table.bigint("views"); // BIGINT table.float("price"); // FLOAT table.decimal("amount", 8, 2); // DECIMAL table.boolean("active"); // BOOLEAN table.date("birth_date"); // DATE table.datetime("created_at"); // DATETIME table.timestamp("updated_at"); // TIMESTAMP table.json("settings"); // JSON table.uuid("uuid"); // UUID table.enum("status", ["active", "inactive"]); ``` ### Column Modifiers ```typescript table.string("email").unique(); table.string("name").nullable(); table.integer("age").default(0); table.string("token").index(); ``` ### Foreign Keys ```typescript table.integer("user_id").unsigned(); table.foreign("user_id").references("id").on("users"); // Shorthand table.foreignId("user_id").constrained("users"); ``` ### Modifying Tables ```typescript schema.table("users", (table) => { table.string("phone").nullable(); table.dropColumn("old_column"); table.renameColumn("old_name", "new_name"); }); ``` ### Dropping Tables ```typescript schema.drop("users"); schema.dropIfExists("users"); ``` ### Helper Methods ```typescript table.timestamps(); // created_at and updated_at table.softDeletes(); // deleted_at table.rememberToken(); // remember_token ``` ## Programmatic Migration ```typescript import { runMigrations, rollbackMigrations, migrateFresh } from "@lara-node/db"; await runMigrations(); await rollbackMigrations(); await migrateFresh(); ``` ## Next Steps - [Seeders](https://laranode.doitrixtech.co.ke/packages/db/seeders) -- Database seeders - [Models](https://laranode.doitrixtech.co.ke/packages/db/models) -- Working with models - [Console Commands](https://laranode.doitrixtech.co.ke/packages/console/built-in) -- Migration commands # Models Models in LaraNode provide an expressive, Eloquent-inspired way to interact with your database. ## Defining Models Extend the `Model` class: ```typescript import { Model } from "@lara-node/db"; class User extends Model { static table = "users"; static primaryKey = "id"; static fillable = ["name", "email", "password"]; static hidden = ["password"]; static timestamps = true; } ``` ## Model Properties | Property | Default | Description | | ------------- | --------------------- | -------------------------- | | `table` | Pluralized class name | Database table name | | `primaryKey` | `'id'` | Primary key column | | `fillable` | `[]` | Mass-assignable attributes | | `guarded` | `[]` | Non-assignable attributes | | `hidden` | `[]` | Hidden from JSON | | `appends` | `[]` | Appended to JSON | | `casts` | `{}` | Attribute type casting | | `timestamps` | `true` | Auto-manage timestamps | | `softDeletes` | `false` | Enable soft deletes | ## Creating Records ```typescript // Create and save const user = await User.create({ name: "John", email: "john@example.com", }); // Create instance and save const user = new User(); user.name = "John"; user.email = "john@example.com"; await user.save(); // First or create const user = await User.firstOrCreate({ email: "john@example.com" }, { name: "John" }); // Update or create const user = await User.updateOrCreate({ email: "john@example.com" }, { name: "John" }); ``` ## Retrieving Records ```typescript // Find by primary key const user = await User.find(1); // All records const users = await User.all(); // First record const user = await User.first(); // With conditions const users = await User.where("active", true).get(); // Multiple conditions const users = await User.where("active", true).where("role", "admin").get(); ``` ## Updating Records ```typescript // Find and update const user = await User.find(1); await user.update({ name: "Jane" }); // Update by query await User.where("role", "user").update({ status: "active" }); // Increment/decrement await User.find(1).increment("login_count"); await User.find(1).decrement("credits", 10); ``` ## Deleting Records ```typescript // Delete instance const user = await User.find(1); await user.delete(); // Delete by query await User.where("inactive", true).delete(); ``` ## Accessors and Mutators Define getters and setters for attributes: ```typescript class User extends Model { static table = "users"; // Accessor getFullName() { return `${this.first_name} ${this.last_name}`; } // Mutator setPasswordAttribute(value: string) { this.attributes.password = hashPassword(value); } } ``` ## Attribute Casting ```typescript class User extends Model { static casts = { is_admin: "boolean", email_verified_at: "date", settings: "json", age: "integer", balance: "float", }; } ``` ## Serialization ```typescript const user = await User.find(1); // To JSON (respects hidden/appends) const json = user.toJSON(); // To plain object const data = user.toObject(); ``` ## Next Steps - [Query Builder](https://laranode.doitrixtech.co.ke/packages/db/query-builder) -- Fluent queries - [Relationships](https://laranode.doitrixtech.co.ke/packages/db/relationships) -- Model relationships - [Traits](https://laranode.doitrixtech.co.ke/packages/db/traits) -- Model traits # Observers Observers allow you to listen to model lifecycle events. ## Creating Observers Extend the `Observer` class: ```typescript import { Observer } from "@lara-node/db"; import { User } from "../Models/User"; @Observe(User) class UserObserver extends Observer { async creating(user: User) { // Before creating } async created(user: User) { // After creating console.log(`User created: ${user.email}`); } async updating(user: User) { // Before updating } async updated(user: User) { // After updating } async deleting(user: User) { // Before deleting } async deleted(user: User) { // After deleting } async restoring(user: User) { // Before restoring (soft delete) } async restored(user: User) { // After restoring } async retrieved(user: User) { // After retrieving from database } } ``` ## Available Events | Event | When | | ----------- | -------------------------------------------- | | `creating` | Before a new model is saved | | `created` | After a new model is saved | | `updating` | Before an existing model is updated | | `updated` | After an existing model is updated | | `saving` | Before a model is saved (create or update) | | `saved` | After a model is saved | | `deleting` | Before a model is deleted | | `deleted` | After a model is deleted | | `restoring` | Before a soft-deleted model is restored | | `restored` | After a soft-deleted model is restored | | `retrieved` | After a model is retrieved from the database | ## Registering Observers ### Using @Observe Decorator ```typescript import { Observe } from "@lara-node/db"; @Observe(User) class UserObserver extends Observer { created(user: User) { // ... } } ``` ### Manual Registration ```typescript User.observe(UserObserver); ``` ## Example: Hash Password ```typescript @Observe(User) class UserObserver extends Observer { async creating(user: User) { if (user.password) { user.password = await hashPassword(user.password); } } async updating(user: User) { if (user.isDirty("password")) { user.password = await hashPassword(user.password); } } } ``` ## Example: Send Welcome Email ```typescript @Observe(User) class UserObserver extends Observer { async created(user: User) { await Mail.to(user.email).send(new WelcomeMailable(user)); } } ``` ## Example: Clear Cache ```typescript @Observe(User) class UserObserver extends Observer { async updated(user: User) { await Cache.del(`user:${user.id}`); } async deleted(user: User) { await Cache.del(`user:${user.id}`); } } ``` ## Next Steps - [Models](https://laranode.doitrixtech.co.ke/packages/db/models) -- Working with models - [Traits](https://laranode.doitrixtech.co.ke/packages/db/traits) -- Model traits - [Events](https://laranode.doitrixtech.co.ke/packages/events) -- Event system # Query Builder The `EloquentBuilder` provides a fluent interface for constructing database queries. ## Basic Queries ```typescript // Get all const users = await User.query().get(); // Where clauses const users = await User.where("active", true).get(); const users = await User.where("age", ">=", 18).get(); // Multiple where const users = await User.where("active", true).where("role", "admin").get(); ``` ## Where Clauses ```typescript // Basic where .where('column', 'value') .where('column', '>', 10) // Or where .orWhere('status', 'pending') // Where in .whereIn('id', [1, 2, 3]) // Where null .whereNull('deleted_at') // Where not null .whereNotNull('deleted_at') // Where between .whereBetween('age', [18, 65]) // Where has (relationships) .whereHas('posts', (query) => { query.where('published', true) }) ``` ## Joins ```typescript .join('posts', 'users.id', '=', 'posts.user_id') .leftJoin('profiles', 'users.id', '=', 'profiles.user_id') ``` ## Ordering ```typescript .orderBy('name') .orderBy('created_at', 'desc') .latest() // orderBy created_at desc .oldest() // orderBy created_at asc ``` ## Limiting Results ```typescript .limit(10) .offset(20) ``` ## Selecting Columns ```typescript .select('id', 'name', 'email') .select(['id', 'name']) .distinct() ``` ## Aggregates ```typescript .count() .max('price') .min('price') .avg('price') .sum('quantity') ``` ## Pagination ```typescript const result = await User.paginate(15, { page: 2 }); // { data: [...], total: 100, page: 2, perPage: 15, lastPage: 7 } ``` ## Chunking ```typescript await User.chunk(100, async (users) => { // Process 100 users at a time }); await User.chunkById(100, async (users) => { // Process by ID }); ``` ## Cursor ```typescript const cursor = User.where("active", true).cursor(); for await (const user of cursor) { // Process one at a time } ``` ## Pluck and Value ```typescript // Get single column values const names = await User.pluck("name"); // Get single value const email = await User.where("id", 1).value("email"); ``` ## First or Fail ```typescript const user = await User.where("email", "test@test.com").firstOrFail(); // Throws if not found const user = await User.sole(); // Throws if not exactly one result ``` ## Eager Loading ```typescript // Load relationships const users = await User.with("posts", "profile").get(); // Constrained eager loading const users = await User.with({ posts: (query) => query.where("published", true), }).get(); ``` ## Soft Deletes ```typescript // Include soft deleted .withTrashed().get() // Only soft deleted .onlyTrashed().get() // Exclude soft deleted (default) .withoutTrashed().get() ``` ## Raw SQL ```typescript .toSql() // Get the SQL string ``` ## MongoDB Queries ```typescript .toMongo() // Convert to MongoDB query format ``` ## Next Steps - [Relationships](https://laranode.doitrixtech.co.ke/packages/db/relationships) -- Model relationships - [Models](https://laranode.doitrixtech.co.ke/packages/db/models) -- Working with models - [DB Facade](https://laranode.doitrixtech.co.ke/packages/db/facade) -- Raw queries # Relationships LaraNode models support Laravel-style relationships for connecting related data. ## Defining Relationships Define relationships in the static `relationships` property: ```typescript class User extends Model { static table = "users"; static relationships = { posts: { type: "hasMany", model: () => Post, foreignKey: "user_id", }, profile: { type: "hasOne", model: () => Profile, foreignKey: "user_id", }, role: { type: "belongsTo", model: () => Role, foreignKey: "role_id", }, }; } ``` ## Has One One-to-one relationship: ```typescript class User extends Model { static relationships = { profile: { type: "hasOne", model: () => Profile, foreignKey: "user_id", }, }; } // Access const user = await User.find(1); const profile = await user.profile; ``` ## Has Many One-to-many relationship: ```typescript class User extends Model { static relationships = { posts: { type: "hasMany", model: () => Post, foreignKey: "user_id", }, }; } // Access const posts = await user.posts; ``` ## Belongs To Inverse of hasOne/hasMany: ```typescript class Post extends Model { static relationships = { author: { type: "belongsTo", model: () => User, foreignKey: "user_id", }, }; } // Access const author = await post.author; ``` ## Belongs To Many Many-to-many relationship with pivot table: ```typescript class User extends Model { static relationships = { roles: { type: "belongsToMany", model: () => Role, pivotTable: "role_user", foreignKey: "user_id", relatedKey: "role_id", }, }; } // Access const roles = await user.roles; // With pivot data for (const role of roles) { console.log(role.pivot.created_at); } ``` ## Has One Through ```typescript class User extends Model { static relationships = { phone: { type: "hasOneThrough", model: () => Phone, through: () => Profile, firstKey: "user_id", secondKey: "profile_id", }, }; } ``` ## Has Many Through ```typescript class Country extends Model { static relationships = { posts: { type: "hasManyThrough", model: () => Post, through: () => User, firstKey: "country_id", secondKey: "user_id", }, }; } ``` ## Eager Loading Load relationships to avoid N+1 queries: ```typescript // Load single relationship const users = await User.with("posts").get(); // Load multiple const users = await User.with("posts", "profile", "roles").get(); // Constrained loading const users = await User.with({ posts: (query) => query.where("published", true).limit(5), }).get(); ``` ## Querying Relationships ```typescript // Where has relationship const users = await User.whereHas("posts", (query) => { query.where("published", true); }).get(); // Where doesnt have const users = await User.whereDoesntHave("posts").get(); ``` ## Complete Example ```typescript class User extends Model { static table = "users"; static fillable = ["name", "email"]; static relationships = { posts: { type: "hasMany", model: () => Post, foreignKey: "user_id", }, profile: { type: "hasOne", model: () => Profile, foreignKey: "user_id", }, roles: { type: "belongsToMany", model: () => Role, pivotTable: "role_user", foreignKey: "user_id", relatedKey: "role_id", }, }; } class Post extends Model { static table = "posts"; static fillable = ["title", "content", "user_id"]; static relationships = { author: { type: "belongsTo", model: () => User, foreignKey: "user_id", }, }; } // Usage const user = await User.with("posts", "profile", "roles").find(1); console.log(user.posts.length); console.log(user.profile.bio); console.log(user.roles.map((r) => r.name)); ``` ## Next Steps - [Models](https://laranode.doitrixtech.co.ke/packages/db/models) -- Working with models - [Query Builder](https://laranode.doitrixtech.co.ke/packages/db/query-builder) -- Fluent queries - [Traits](https://laranode.doitrixtech.co.ke/packages/db/traits) -- Model traits # Seeders Seeders populate your database with test data. ## Creating Seeders ```bash pnpm exec artisan make:seeder UserSeeder ``` ## Writing Seeders ```typescript // database/seeders/UserSeeder.ts import { DB } from "@lara-node/db"; export class UserSeeder { async run() { await DB.table("users").insert({ name: "John Doe", email: "john@example.com", password: "hashed_password", }); } } ``` ## Using Model Factories ```typescript import { User } from "../app/Models/User"; export class UserSeeder { async run() { await User.create({ name: "John Doe", email: "john@example.com", password: "hashed_password", }); // Create multiple for (let i = 0; i < 10; i++) { await User.create({ name: `User ${i}`, email: `user${i}@example.com`, password: "hashed_password", }); } } } ``` ## Running Seeders ```bash # Run all seeders pnpm exec artisan db:seed # Run specific seeder pnpm exec artisan db:seed --class=UserSeeder ``` ## Programmatic Seeding ```typescript import { runSeeders } from "@lara-node/db"; import { UserSeeder } from "./seeders/UserSeeder"; await runSeeders([UserSeeder]); ``` ## Database Seeder Create a main seeder that calls others: ```typescript // database/seeders/DatabaseSeeder.ts import { UserSeeder } from "./UserSeeder"; import { PostSeeder } from "./PostSeeder"; export class DatabaseSeeder { async run() { await new UserSeeder().run(); await new PostSeeder().run(); } } ``` ## Wiping Database ```bash pnpm exec artisan db:wipe ``` ## Next Steps - [Migrations](https://laranode.doitrixtech.co.ke/packages/db/migrations) -- Database migrations - [Models](https://laranode.doitrixtech.co.ke/packages/db/models) -- Working with models - [Console Commands](https://laranode.doitrixtech.co.ke/packages/console/built-in) -- Database commands # Traits Traits are reusable behaviors that can be applied to models. ## Available Traits | Trait | Description | | ------------- | ----------------------------------- | | `SoftDeletes` | Soft delete support | | `Timestamps` | Auto-manage created\_at/updated\_at | | `Sluggable` | Auto-generate URL slugs | | `Sortable` | Sorting support | | `Searchable` | Full-text search | | `Cacheable` | Query caching | ## Applying Traits Use the `@use()` decorator: ```typescript import { Model, use } from "@lara-node/db"; import { SoftDeletes, Timestamps, Sluggable } from "@lara-node/db"; @use(SoftDeletes, Timestamps, Sluggable) class Post extends Model { static table = "posts"; static fillable = ["title", "content"]; static slugSource = "title"; } ``` ## SoftDeletes Adds soft delete support: ```typescript @use(SoftDeletes) class User extends Model { static softDeletes = true; } // Usage await user.delete(); // Soft deletes await user.restore(); // Restores // Queries User.withTrashed().get(); // Include deleted User.onlyTrashed().get(); // Only deleted User.withoutTrashed().get(); // Exclude deleted ``` ## Timestamps Auto-manages timestamps: ```typescript @use(Timestamps) class User extends Model { static timestamps = true; } // Automatically sets created_at and updated_at ``` ## Sluggable Generates URL-friendly slugs: ```typescript @use(Sluggable) class Post extends Model { static slugSource = "title"; static slugField = "slug"; } // "Hello World" -> "hello-world" ``` ## Sortable Adds sorting support: ```typescript @use(Sortable) class Product extends Model { static sortable = ["name", "price", "created_at"]; } // Usage Product.orderBy("name", "asc").get(); ``` ## Searchable Full-text search: ```typescript @use(Searchable) class Post extends Model { static searchable = ["title", "content"]; } // Usage const results = await Post.search("keyword").get(); ``` ## Cacheable Caches query results: ```typescript @use(Cacheable) class User extends Model { static cacheTTL = 3600; // 1 hour } // Queries are automatically cached ``` ## Next Steps - [Models](https://laranode.doitrixtech.co.ke/packages/db/models) -- Working with models - [Observers](https://laranode.doitrixtech.co.ke/packages/db/observers) -- Model observers - [Relationships](https://laranode.doitrixtech.co.ke/packages/db/relationships) -- Model relationships # Events Package The `@lara-node/events` package provides an event dispatcher with listeners, subscribers, and WebSocket broadcasting. ## Installation ```bash pnpm add @lara-node/events @lara-node/core reflect-metadata ``` ## Overview Features include: - **Event dispatcher** with publish/subscribe - **Listeners** with decorators - **Event subscribers** for grouping - **Queueable listeners** - **Transaction-aware** events - **WebSocket broadcasting** - **Channel types** (public, private, presence) ## Quick Start ### Define an Event ```typescript class UserRegistered { constructor(public user: User) {} } ``` ### Define a Listener ```typescript import { ListensTo } from "@lara-node/events"; @ListensTo(UserRegistered) class SendWelcomeEmail { async handle(event: UserRegistered) { await Mail.to(event.user.email).send(new WelcomeMail(event.user)); } } ``` ### Dispatch an Event ```typescript import { event } from "@lara-node/events"; await event(new UserRegistered(user)); ``` ## Key Exports | Export | Description | | ----------------- | -------------------------- | | `EventDispatcher` | Event dispatcher | | `event()` | Dispatch helper | | `on()` | Register listener | | `once()` | Register one-time listener | | `off()` | Remove listener | | `@ListensTo()` | Listener decorator | | `@ShouldQueue` | Queue listener | | `@AfterCommit` | After transaction | | `@Subscriber` | Subscriber decorator | | `Broadcast` | Broadcast facade | | `Channel` | Channel types | ## Next Steps - [Listeners](https://laranode.doitrixtech.co.ke/packages/events/listeners) -- Event listeners - [Subscribers](https://laranode.doitrixtech.co.ke/packages/events/subscribers) -- Event subscribers - [Broadcasting](https://laranode.doitrixtech.co.ke/packages/events/broadcasting) -- WebSocket broadcasting # Broadcasting Broadcast events to clients via WebSockets. ## Channel Types ```typescript import { PublicChannel, PrivateChannel, PresenceChannel } from "@lara-node/events"; // Public channel (anyone can listen) new PublicChannel("chat"); // Private channel (requires auth) new PrivateChannel(`user.${userId}`); // Presence channel (tracks who is online) new PresenceChannel(`room.${roomId}`); ``` ## Broadcasting Events ```typescript import { ShouldBroadcast, BroadcastAs, BroadcastToOthers } from "@lara-node/events"; @ShouldBroadcast() class MessageSent { constructor(public message: Message) {} broadcastOn() { return new PrivateChannel(`chat.${this.message.roomId}`); } } ``` ## Broadcast Decorators ```typescript @ShouldBroadcast() @BroadcastAs("new-message") @BroadcastToOthers() class MessageSent { broadcastOn() { return new Channel("chat"); } broadcastWith() { return { id: this.message.id, text: this.message.text, user: this.message.user.name, }; } } ``` ## Broadcast Conditions ```typescript @ShouldBroadcast() class OrderStatusChanged { broadcastWhen() { return this.order.status === "shipped"; } } ``` ## Broadcast Facade ```typescript import { Broadcast } from "@lara-node/events"; Broadcast.channel("chat").emit("message", data); Broadcast.toOthers().channel("chat").emit("message", data); ``` ## Configuration ```dotenv BROADCAST_DRIVER=websocket ``` ## Next Steps - [Listeners](https://laranode.doitrixtech.co.ke/packages/events/listeners) -- Event listeners - [Queue](https://laranode.doitrixtech.co.ke/packages/queue) -- Queue system - [Horizon](https://laranode.doitrixtech.co.ke/packages/horizon) -- Queue monitoring # Event Listeners Listeners handle events when they are dispatched. ## Creating Listeners ```typescript import { ListensTo } from "@lara-node/events"; @ListensTo(UserRegistered) class SendWelcomeEmail { async handle(event: UserRegistered) { await Mail.to(event.user.email).send(new WelcomeMail(event.user)); } } ``` ## Multiple Listeners Multiple listeners can listen to the same event: ```typescript @ListensTo(UserRegistered) class SendWelcomeEmail { async handle(event: UserRegistered) { // Send email } } @ListensTo(UserRegistered) class NotifyAdmin { async handle(event: UserRegistered) { // Notify admin } } ``` ## Listening to Multiple Events ```typescript @ListensTo(UserRegistered) @ListensTo(UserUpdated) class UpdateSearchIndex { async handle(event: UserRegistered | UserUpdated) { // Update search index } } ``` ## Registering Listeners Manually ```typescript import { on, event } from "@lara-node/events"; on(UserRegistered, async (event) => { console.log("User registered:", event.user.email); }); // Dispatch await event(new UserRegistered(user)); ``` ## Once Listeners ```typescript import { once } from "@lara-node/events"; once(AppStarted, async () => { console.log("This runs only once"); }); ``` ## Removing Listeners ```typescript import { off } from "@lara-node/events"; off(UserRegistered, listenerFunction); ``` ## Event Naming Customize event names: ```typescript import { EventName } from "@lara-node/events"; @EventName("user.registered") class UserRegistered { constructor(public user: User) {} } ``` ## Next Steps - [Subscribers](https://laranode.doitrixtech.co.ke/packages/events/subscribers) -- Event subscribers - [Queueable Listeners](https://laranode.doitrixtech.co.ke/packages/events/queueable) -- Queue-backed listeners - [Broadcasting](https://laranode.doitrixtech.co.ke/packages/events/broadcasting) -- WebSocket broadcasting # Queueable Listeners Run listeners asynchronously via the queue system. ## Making a Listener Queueable ```typescript import { ListensTo, ShouldQueue } from "@lara-node/events"; @ListensTo(UserRegistered) @ShouldQueue() class SendWelcomeEmail { async handle(event: UserRegistered) { await Mail.to(event.user.email).send(new WelcomeMail(event.user)); } } ``` ## Queue Options ```typescript @ShouldQueue({ queue: "emails", connection: "redis", tries: 3, timeout: 60, }) class SendWelcomeEmail { // ... } ``` ## After Commit Run listener after database transaction commits: ```typescript import { ListensTo, ShouldQueue, AfterCommit } from "@lara-node/events"; @ListensTo(OrderCreated) @ShouldQueue() @AfterCommit() class SendOrderConfirmation { async handle(event: OrderCreated) { // Only runs after transaction commits await Mail.to(event.order.user.email).send(new OrderConfirmationMail(event.order)); } } ``` ## QueueableListener Base ```typescript import { QueueableListener } from "@lara-node/events"; class SendWelcomeEmail extends QueueableListener { async handle(event: UserRegistered) { // Queueable by default } } ``` ## Next Steps - [Listeners](https://laranode.doitrixtech.co.ke/packages/events/listeners) -- Event listeners - [Subscribers](https://laranode.doitrixtech.co.ke/packages/events/subscribers) -- Event subscribers - [Broadcasting](https://laranode.doitrixtech.co.ke/packages/events/broadcasting) -- WebSocket broadcasting # Event Subscribers Subscribers group multiple related listeners in a single class. ## Creating Subscribers ```typescript import { Subscriber } from "@lara-node/events"; @Subscriber() class UserEventSubscriber { async onUserRegistered(event: UserRegistered) { await Mail.to(event.user.email).send(new WelcomeMail(event.user)); } async onUserUpdated(event: UserUpdated) { await Cache.del(`user:${event.user.id}`); } async onUserDeleted(event: UserDeleted) { await Cache.del(`user:${event.user.id}`); } } ``` ## Subscribing ```typescript import { EventDispatcher } from "@lara-node/events"; const dispatcher = new EventDispatcher(); dispatcher.subscribe(UserEventSubscriber); ``` ## Event Method Naming Methods follow the pattern `on`: ```typescript onUserRegistered(event: UserRegistered) onUserUpdated(event: UserUpdated) onUserDeleted(event: UserDeleted) ``` ## Complete Example ```typescript @Subscriber() class OrderEventSubscriber { async onOrderCreated(event: OrderCreated) { await Mail.to(event.order.user.email).send(new OrderConfirmationMail(event.order)); } async onOrderPaid(event: OrderPaid) { await event.order.update({ status: "paid" }); } async onOrderShipped(event: OrderShipped) { await Notification.send(event.order.user, "Your order has been shipped!"); } async onOrderCancelled(event: OrderCancelled) { await event.order.update({ status: "cancelled" }); } } ``` ## Next Steps - [Listeners](https://laranode.doitrixtech.co.ke/packages/events/listeners) -- Event listeners - [Queueable Listeners](https://laranode.doitrixtech.co.ke/packages/events/queueable) -- Queue-backed listeners - [Broadcasting](https://laranode.doitrixtech.co.ke/packages/events/broadcasting) -- WebSocket broadcasting # Exports Package The `@lara-node/exports` package provides PDF, Excel, and CSV export utilities. ## Installation ```bash pnpm add @lara-node/exports puppeteer xlsx ``` ## Overview Features include: - **PDF export** using Puppeteer — HTML to PDF with template support - **Excel export** using SheetJS — array of objects to `.xlsx` - **CSV export** — array of objects to CSV string - **Template support** — render from HTML/JSON files with variable interpolation - **Custom delimiters** — use any delimiter pair (e.g. `{{ }}`, `<% %>`) ## Quick Start ```typescript import { toPDF, toExcel, toCSV } from "@lara-node/exports"; // PDF from HTML string const pdf = await toPDF("

Hello

", { format: "A4" }); // PDF from template file const pdf2 = await toPDFFromTemplate("invoice.html", { title: "Invoice #1" }); // Excel from data const xlsx = toExcel([ { name: "Alice", email: "alice@test.com" }, { name: "Bob", email: "bob@test.com" }, ]); // CSV from data const csv = toCSV([ { name: "Alice", email: "alice@test.com" }, ]); ``` ## Key Exports | Export | Description | | ----------------------- | ---------------------------------- | | `toPDF()` | Generate PDF from HTML string | | `toPDFFromTemplate()` | Generate PDF from HTML template | | `closeBrowser()` | Close the Puppeteer browser | | `toExcel()` | Generate Excel from array of data | | `toExcelFromTemplate()` | Generate Excel using column config | | `toCSV()` | Generate CSV from array of data | | `toCSVFromTemplate()` | Generate CSV using column config | ## Next Steps Explore the exports package to generate PDFs, Excel files, and CSV data for your application. # CSV Export Generate and parse CSV strings using `@lara-node/csv`. ```typescript import { CSV, CsvExportable, CsvImportable, CsvWriter, CsvReader, WithCsvHeadings, WithCsvMapping, WithCsvEncoding, WithCsvSorting, WithCsvFilters, WithCsvTransform, WithCsvStartRow, WithCsvChunkReading, WithCsvValidation, } from '@lara-node/csv'; ``` --- ## Quick Start ```typescript import { CSV } from '@lara-node/csv'; // Parse a CSV string const rows = CSV.parse('name,age\nAlice,30\nBob,25'); // [{ name: 'Alice', age: '30' }, { name: 'Bob', age: '25' }] // Build a CSV string from an array const csv = CSV.fromArray([{ name: 'Alice', age: 30 }]); // "name,age\nAlice,30" ``` --- ## Export — `CsvExportable` Implement `CsvExportable` (plus optional concern interfaces) on any class. ```typescript class UsersExport implements CsvExportable, WithCsvHeadings, WithCsvMapping, WithCsvSorting, WithCsvFilters, WithCsvTransform { async collection() { return db.users.findAll(); } headings() { return ['ID', 'Name', 'Email', 'Joined']; } map(user: Record) { return [user.id, user.name, user.email, user.createdAt]; } sortBy() { return (a, b) => String(a.name).localeCompare(String(b.name)); } async filter(row: Record) { return row.active === true; } async transform(row: Record) { return { ...row, email: String(row.email).toLowerCase() }; } } // Download via Express app.get('/users.csv', async (req, res) => { await CSV.download(new UsersExport(), 'users.csv', res); }); // Save to disk await CSV.store(new UsersExport(), '/tmp/users.csv'); // Get raw string const csvString = await CSV.raw(new UsersExport()); // Get as Buffer const buf = await CSV.toBuffer(new UsersExport()); ``` ### Optional Export Concerns | Interface | Method | Description | | ------------------ | ------------------------------------------------- | -------------------------------------- | | `WithCsvHeadings` | `headings(): string[]` | Explicit column headers | | `WithCsvMapping` | `map(row): unknown[]` | Custom value extraction per row | | `WithCsvEncoding` | `encoding(): 'utf-8' | 'utf-8-bom'` | Output encoding (BOM for Excel compat) | | `WithCsvSorting` | `sortBy(): Comparator` | Sort collection before export | | `WithCsvFilters` | `filter(row, index): boolean | Promise` | Skip rows during export | | `WithCsvTransform` | `transform(row, index): Record | Promise` | Mutate rows before serialization | --- ## Import — `CsvImportable` ```typescript class UsersImport implements CsvImportable, WithCsvStartRow, WithCsvValidation { startRow() { return 2; // skip header row (default) } validate(row: Record, index: number) { if (!row.email?.includes('@')) return false; // silently skip if (!row.name) return 'Name is required'; // throws Error return true; } async model(row: Record) { await db.users.create({ name: row.name, email: row.email }); } } // Import from file await CSV.import(new UsersImport(), '/uploads/users.csv'); // Import from string await CSV.import(new UsersImport(), rawCsv, { type: 'string' }); // Tab-separated await CSV.import(new UsersImport(), '/uploads/data.tsv', { delimiter: '\t' }); ``` ### Optional Import Concerns | Interface | Method | Description | | --------------------- | ------------------------------------------------------- | ----------------------------------- | | `WithCsvStartRow` | `startRow(): number` | 1-based first data row (default: 2) | | `WithCsvChunkReading` | `chunkSize(): number` | Process in chunks for large files | | `WithCsvValidation` | `validate(row, index): boolean | string | Promise<...>` | `false` skips, `string` throws | --- ## Static Utilities (no class needed) ### `CSV.parse(csv, options?)` Parse a CSV string into keyed records. ```typescript const rows = CSV.parse('a,b\n1,2\n3,4'); // [{ a: '1', b: '2' }, { a: '3', b: '4' }] const tsv = CSV.parse('a\tb\n1\t2', { delimiter: '\t' }); ``` ### `CSV.parseBuffer(buf, options?)` Parse a UTF-8/UTF-8-BOM Buffer into records. ```typescript const rows = CSV.parseBuffer(fs.readFileSync('data.csv')); ``` ### `CSV.fromArray(data, options?)` Build a CSV string from a plain array. ```typescript const csv = CSV.fromArray( [{ name: 'Alice', score: 99 }], { headers: ['name', 'score'], encoding: 'utf-8-bom' }, ); ``` ### `CSV.count(csv)` Count data rows (excludes header). ```typescript CSV.count('name,age\nAlice,30\nBob,25'); // 2 ``` ### `CSV.columns(csv)` Extract column names from the header row. ```typescript CSV.columns('id,name,email\n1,Alice,a@b.com'); // ['id', 'name', 'email'] ``` ### `CSV.filter(csv, predicate, options?)` Filter rows using a predicate. ```typescript const adults = CSV.filter(csv, (row) => Number(row.age) >= 18); ``` ### `CSV.transform(csv, fn, options?)` Map over rows. ```typescript const upper = CSV.transform(csv, (row) => ({ ...row, name: row.name.toUpperCase(), })); ``` ### `CSV.sort(csv, columns, options?)` Sort by one or more columns (numeric or lexicographic). ```typescript const sorted = CSV.sort(csv, [ { key: 'lastName', direction: 'asc' }, { key: 'age', direction: 'desc' }, ]); ``` ### `CSV.select(csv, columns, options?)` Project specific columns. ```typescript const slim = CSV.select(csv, ['id', 'email']); ``` ### `CSV.deduplicate(csv, keys?, options?)` Remove duplicate rows, optionally keyed on specific columns. ```typescript const unique = CSV.deduplicate(csv, ['email']); ``` ### `CSV.merge(csvStrings, options?)` Merge multiple CSV strings. Only the first header is kept by default. ```typescript const combined = CSV.merge([januaryCsv, februaryCsv]); ``` ### `CSV.columnStats(csv, column)` Compute statistics for a numeric column. ```typescript const stats = CSV.columnStats(csv, 'price'); // { min: 4.99, max: 99.99, sum: 1234.50, avg: 24.69, count: 50 } ``` ### `CSV.stream(exportable, options?)` Async generator for streaming large exports. ```typescript res.setHeader('Content-Type', 'text/csv'); for await (const chunk of CSV.stream(new UsersExport(), { chunkSize: 500 })) { res.write(chunk); } res.end(); ``` --- ## Low-level API ### `CsvWriter` ```typescript const writer = new CsvWriter({ delimiter: ';', encoding: 'utf-8-bom' }); const line = writer.serializeRow(['Alice', "O'Brien", 42]); const csv = writer.serialize( [['Alice', 30], ['Bob', 25]], ['Name', 'Age'], ); for await (const chunk of writer.stream(rows, headers, 1000)) { response.write(chunk); } ``` ### `CsvReader` ```typescript const reader = new CsvReader({ delimiter: '\t', headers: true }); const rows = reader.parse(tsvString); ``` --- ## Options ### `CsvOptions` | Option | Type | Default | Description | | ---------------- | ----------------------- | --------- | -------------------------------- | | `delimiter` | `string` | `','` | Column delimiter | | `lineEnding` | `'\n' | '\r\n'` | `'\n'` | Line-ending sequence | | `encoding` | `'utf-8' | 'utf-8-bom'` | `'utf-8'` | BOM for Excel compatibility | | `quoteChar` | `string` | `'"'` | Quote character | | `escapeChar` | `string` | `'"'` | Quote-escape character (doubled) | | `includeHeaders` | `boolean` | `true` | Write the header row | | `nullValue` | `string` | `''` | Null/undefined representation | | `booleanTrue` | `string` | `'1'` | Boolean `true` representation | | `booleanFalse` | `string` | `'0'` | Boolean `false` representation | ### `ParseOptions` | Option | Type | Default | Description | | ----------- | --------- | ------- | -------------------------- | | `delimiter` | `string` | `','` | Column delimiter | | `headers` | `boolean` | `true` | First row contains headers | | `quoteChar` | `string` | `'"'` | Quote character | ### `StreamOptions` | Option | Type | Default | Description | | ---------------- | -------- | ------- | ------------------------ | | *all CsvOptions* | | | Inherits all CSV options | | `chunkSize` | `number` | `1000` | Rows per emitted chunk | # Excel Export Generate Excel (`.xlsx`) files using `@lara-node/excel`, powered by [ExcelJS](https://github.com/exceljs/exceljs){rel=""nofollow""}. ```typescript import { Excel, Exportable, Importable, WithHeadings, WithMapping, WithStyles, WithColumnFormatting, WithColumnWidths, WithRowHeights, WithTitle, WithProperties, WithEvents, WithAutoFilter, WithFrozenRows, WithFrozenColumns, WithTabColor, WithProtection, WithConditionalFormatting, WithMultipleSheets, WithBatchInserts, WithChunkReading, WithStartRow, BeforeImport, AfterImport, } from '@lara-node/excel'; ``` **Peer dependency:** `express` --- ## Quick Start ```typescript import { Excel } from '@lara-node/excel'; // No-class export from a plain array const buf = await Excel.fromArray( [{ name: 'Alice', score: 99 }, { name: 'Bob', score: 85 }], { sheetName: 'Scores', headers: ['name', 'score'] }, ); // No-class import const rows = await Excel.toArray('/uploads/data.xlsx'); ``` --- ## Export — `Exportable` Implement `Exportable` on any class to unlock Excel export. ```typescript class UsersExport implements Exportable, WithHeadings, WithMapping, WithStyles, WithColumnFormatting, WithTitle, WithAutoFilter, WithFrozenRows, WithColumnWidths, WithTabColor { async collection() { return db.users.findAll(); } title() { return 'Users'; } headings() { return ['ID', 'Name', 'Email', 'Joined', 'Revenue']; } map(user: Record) { return [user.id, user.name, user.email, user.createdAt, user.revenue]; } columnFormats() { return { 5: '#,##0.00' }; // column E as currency } columnWidths() { return { 1: 8, 2: 25, 3: 35, 4: 18, 5: 14 }; } autoFilter() { return true; } frozenRows() { return 1; } tabColor() { return 'FF4472C4'; } styles(worksheet: ExcelJS.Worksheet) { worksheet.getRow(1).font = { bold: true }; worksheet.getRow(1).eachCell((cell) => { cell.fill = { type: 'pattern', pattern: 'solid', fgColor: { argb: 'FF4472C4' } }; cell.font = { bold: true, color: { argb: 'FFFFFFFF' } }; }); return worksheet; } } // Download via Express app.get('/users.xlsx', async (req, res) => { await Excel.download(new UsersExport(), 'users.xlsx', res); }); // Save to disk await Excel.store(new UsersExport(), '/tmp/users.xlsx'); // Get as Buffer const buf = await Excel.raw(new UsersExport()); // Get as base64 const b64 = await Excel.base64(new UsersExport()); // Background job await Excel.queue(new UsersExport(), 'users.xlsx', '/var/exports/'); ``` ### Export Concerns | Interface | Method | Description | | --------------------------- | --------------------------------------------------- | ---------------------------------- | | `Exportable` | `collection()` | **Required.** Return the data rows | | `WithHeadings` | `headings(): string[]` | Column headers | | `WithMapping` | `map(row): unknown[]` | Custom column extraction | | `WithStyles` | `styles(worksheet)` | Apply ExcelJS styles after rows | | `WithColumnFormatting` | `columnFormats(): Record` | Number/date format per column | | `WithColumnWidths` | `columnWidths(): Record` | Column widths in characters | | `WithRowHeights` | `rowHeights(): Record` | Row heights in points | | `WithTitle` | `title(): string` | Worksheet name | | `WithProperties` | `properties(): Partial` | Workbook metadata | | `WithEvents` | `onRow(row, data)` | Hook into each row write | | `WithAutoFilter` | `autoFilter(): boolean | string` | Enable auto-filter on headings | | `WithFrozenRows` | `frozenRows(): number` | Freeze top N rows | | `WithFrozenColumns` | `frozenColumns(): number` | Freeze left N columns | | `WithTabColor` | `tabColor(): string` | ARGB hex for sheet tab | | `WithProtection` | `protection(): string | WorksheetProtectionOptions` | Protect the worksheet | | `WithConditionalFormatting` | `conditionalFormats(): ConditionalFormattingRule[]` | Conditional formatting rules | --- ## Multiple Sheets — `WithMultipleSheets` ```typescript class ActiveUsersSheet implements Exportable, WithHeadings, WithTitle { title() { return 'Active'; } headings() { return ['Name', 'Email']; } async collection() { return db.users.findAll({ where: { active: true } }); } } class InactiveUsersSheet implements Exportable, WithHeadings, WithTitle { title() { return 'Inactive'; } headings() { return ['Name', 'Email', 'LastLogin']; } async collection() { return db.users.findAll({ where: { active: false } }); } } class MultiSheetExport implements WithMultipleSheets { sheets() { return [new ActiveUsersSheet(), new InactiveUsersSheet()]; } } await Excel.download(new MultiSheetExport(), 'users-report.xlsx', res); ``` --- ## Import — `Importable` ```typescript class UsersImport implements Importable, WithStartRow, WithBatchInserts, BeforeImport, AfterImport { startRow() { return 2; } batchSize() { return 100; } beforeImport(workbook: ExcelJS.Workbook) { console.log(`Importing ${workbook.worksheets[0].rowCount - 1} rows`); } async batchInsert(rows: Record[]) { await db.users.bulkCreate(rows); } async model(row: Record) { // used when batchInsert is not implemented await db.users.create(row); } afterImport(workbook: ExcelJS.Workbook) { console.log('Import complete'); } } // Import from file await Excel.import(new UsersImport(), '/uploads/users.xlsx'); // Import from Buffer await Excel.import(new UsersImport(), fileBuffer, { type: 'buffer' }); ``` ### Import Concerns | Interface | Method | Description | | ------------------ | --------------------------------- | ----------------------------------- | | `Importable` | `model(row)` | **Required.** Process each row | | `WithStartRow` | `startRow(): number` | 1-based first data row (default: 2) | | `WithBatchInserts` | `batchSize() + batchInsert(rows)` | Batch processing | | `WithChunkReading` | `chunkSize(): number` | Read in chunks (streaming) | | `BeforeImport` | `beforeImport(workbook)` | Called before row processing | | `AfterImport` | `afterImport(workbook)` | Called after all rows processed | --- ## Static Helpers ### `Excel.fromArray(data, options?)` Create a workbook from a plain array. ```typescript const buf = await Excel.fromArray( [{ product: 'Widget', qty: 100, price: 4.99 }], { sheetName: 'Inventory', headers: ['product', 'qty', 'price'] }, ); ``` ### `Excel.toArray(source, sheetIndex?, options?)` Parse into an array of objects (first row = headers). ```typescript const rows = await Excel.toArray('/uploads/data.xlsx'); const rows = await Excel.toArray(buffer, 0, { type: 'buffer' }); ``` ### `Excel.sheets(source, options?)` List sheet names. ```typescript const names = await Excel.sheets('/uploads/report.xlsx'); // ['Summary', 'Details', 'Charts'] ``` --- ## Worksheet Protection ```typescript class ProtectedExport implements Exportable, WithHeadings, WithProtection { protection() { return { password: 's3cr3t', selectLockedCells: true, insertRows: false, deleteRows: false, }; } } ``` ## Conditional Formatting ```typescript class FormattedExport implements Exportable, WithHeadings, WithConditionalFormatting { conditionalFormats() { return [ { ref: 'E2:E1000', rules: [{ type: 'cellIs', operator: 'lessThan', formulae: ['0'], style: { fill: { type: 'pattern', pattern: 'solid', bgColor: { argb: 'FFFF0000' } } }, }], }, ]; } } ``` # PDF Export Generate PDF documents using `@lara-node/pdf`, powered by [Puppeteer](https://pptr.dev/){rel=""nofollow""}. ```typescript import { PDF, closeBrowser } from '@lara-node/pdf'; import type { PaperSize, Orientation, MarginOptions, ViewportOptions, HeaderFooterOptions, WatermarkOptions, PageMetadata, ScreenshotOptions, } from '@lara-node/pdf'; ``` --- ## Quick Start ```typescript import { PDF } from '@lara-node/pdf'; // From an HTML string with variable interpolation const pdf = PDF.loadHTML('

Invoice #{{number}}

', { number: 1042 }); // Configure via fluent API pdf .setPaper('a4', 'portrait') .setMargins({ top: '20mm', bottom: '20mm', left: '15mm', right: '15mm' }) .addPageNumbers(); // Download via Express app.get('/invoice/:id', async (req, res) => { await pdf.download(res, `invoice-${req.params.id}.pdf`); }); // Save to disk await pdf.save('/tmp/invoice.pdf'); // Get as Buffer const buf = await pdf.output(); // Get as base64 const b64 = await pdf.base64(); ``` --- ## Loading HTML ### `PDF.loadHTML(html, data?)` Create from an HTML string with `{{variable}}` interpolation. ```typescript const pdf = PDF.loadHTML(`

Hello, {{name}}!

Order total: {{total}}

`, { name: 'Alice', total: '$99.00' }); ``` ### `PDF.loadView(filePath, data?)` Read an HTML file and interpolate variables. ```typescript const pdf = await PDF.loadView('/views/invoice.html', { invoice, company }); ``` ### `PDF.loadFile(filePath)` Read an HTML file without interpolation. ```typescript const pdf = await PDF.loadFile('/reports/static-report.html'); ``` ### `PDF.loadUrl(url)` Navigate to a URL and capture the rendered page. ```typescript const pdf = await PDF.loadUrl('https://example.com/report'); ``` --- ## Configuration (Fluent API) All config methods return `this` for chaining. ### Paper & Orientation ```typescript pdf.setPaper('a4', 'portrait'); // 'a3' | 'a4' | 'a5' | 'letter' | 'legal' | 'tabloid' pdf.setOrientation('landscape'); ``` ### Margins ```typescript pdf.setMargins({ top: '20mm', right: '15mm', bottom: '20mm', left: '15mm' }); ``` ### Viewport ```typescript pdf.setViewport({ width: 1280, height: 900, deviceScaleFactor: 2 }); ``` ### Scale ```typescript pdf.setScale(0.9); // 0.1 – 2.0 ``` ### Page Ranges ```typescript pdf.setPageRanges('1-3, 5'); ``` ### Background Colour ```typescript pdf.setBackground('#f8f9fa'); ``` ### Extra CSS ```typescript pdf.addStyles(` body { font-size: 12pt; } table { border-collapse: collapse; } td, th { border: 1px solid #ccc; padding: 4px 8px; } `); ``` ### Extra JavaScript ```typescript pdf.addScript(`document.querySelectorAll('.no-print').forEach(el => el.remove());`); ``` ### Header & Footer Use Puppeteer's special `pageNumber` / `totalPages` classes. ```typescript pdf.setHeader( `
CONFIDENTIAL — {{company}}
`, { height: '15mm' }, ); pdf.setFooter( `
Page of
`, { height: '10mm' }, ); ``` ### Page Numbers (convenience) ```typescript pdf.addPageNumbers(); // "1 / 5" pdf.addPageNumbers('Page {pageNumber} of {totalPages}', { height: '12mm' }); ``` ### Watermark ```typescript pdf.setWatermark('CONFIDENTIAL', { opacity: 0.12, fontSize: 72, rotation: -45, color: '#cc0000', }); ``` ### Document Metadata ```typescript pdf.setMetadata({ title: 'Invoice #1042', author: 'Acme Corp', subject: 'Monthly Invoice', keywords: 'invoice, billing', creator: 'Lara-Node PDF', }); ``` ### Low-level Puppeteer options ```typescript pdf.setOption('printBackground', false); pdf.setOptions({ preferCSSPageSize: true, omitBackground: true }); ``` --- ## Output ### `pdf.output(format?)` Returns `Buffer` (default) or `base64`. ```typescript const buf = await pdf.output(); const buf = await pdf.output('buffer'); const b64 = await pdf.output('base64'); ``` ### `pdf.base64()` Convenience alias for `pdf.output('base64')`. ### `pdf.save(filePath)` ```typescript await pdf.save('/var/reports/report-2024.pdf'); ``` ### `pdf.download(res, filename)` Send as attachment via Express. ```typescript await pdf.download(res, 'report.pdf'); ``` ### `pdf.stream(res, filename)` Render inline in the browser (`Content-Disposition: inline`). ```typescript await pdf.stream(res, 'report.pdf'); ``` ### `pdf.screenshot(options?)` Capture a screenshot instead of a PDF. ```typescript const png = await pdf.screenshot({ type: 'png', fullPage: true }); const jpg = await pdf.screenshot({ type: 'jpeg', quality: 85 }); res.setHeader('Content-Type', 'image/png'); res.end(png); ``` --- ## Batch Generation Generate multiple PDFs in parallel. ```typescript await PDF.batch([ { pdf: PDF.loadHTML(template, dataA), filePath: '/tmp/report-a.pdf' }, { pdf: PDF.loadHTML(template, dataB), filePath: '/tmp/report-b.pdf' }, { pdf: await PDF.loadView('/views/invoice.html', invoiceData), filePath: '/tmp/invoice.pdf' }, ]); ``` --- ## Browser Lifecycle A singleton Puppeteer browser is shared across all operations. ```typescript import { closeBrowser } from '@lara-node/pdf'; process.on('SIGTERM', async () => { await closeBrowser(); process.exit(0); }); // Or via the static method: await PDF.closeBrowser(); ``` --- ## Supported Paper Sizes | Size | Dimensions | | --------- | ------------ | | `a3` | 297 × 420 mm | | `a4` | 210 × 297 mm | | `a5` | 148 × 210 mm | | `letter` | 216 × 279 mm | | `legal` | 216 × 356 mm | | `tabloid` | 279 × 432 mm | # Templates The exports package supports template-based rendering for PDF, Excel, and CSV exports. ## Template Files Place HTML template files in a `templates/` directory at your project root. ```html

{{ title }}

Amount: {{ amount }}

Date: {{ date }}

``` ## Variable Interpolation Variables are interpolated using delimiters. The default is `{{ }}`. ```typescript const pdf = await toPDFFromTemplate("invoice.html", { title: "Invoice #1234", amount: "$99.00", }); ``` ## Custom Delimiters You can use custom delimiter pairs: ```typescript const pdf = await toPDFFromTemplate("invoice.html", data, { delimiters: ["<%", "%>"], }); ``` Now your template would use `<% variable %>` instead of `{{ variable }}`. # Horizon Package The `@lara-node/horizon` package provides a beautiful queue monitoring dashboard, inspired by Laravel Horizon. ## Installation ```bash pnpm add @lara-node/horizon @lara-node/core @lara-node/queue express ``` ## Overview Features include: - **Queue monitoring** dashboard - **Worker management** (start, pause, resume, stop) - **Job metrics** and statistics - **Failed job** management - **Scheduler task** monitoring - **Real-time** updates ## Quick Start Horizon is automatically registered when you install the `HorizonServiceProvider`: ```typescript import { HorizonServiceProvider } from "@lara-node/horizon"; app.register(HorizonServiceProvider); ``` Visit `/horizon` to access the dashboard. ## Key Exports | Export | Description | | ------------------------ | ----------------- | | `HorizonManager` | Worker manager | | `horizonMetrics` | Metrics storage | | `HorizonDashboard` | HTTP dashboard | | `HorizonServiceProvider` | Auto-registration | ## Next Steps - [Configuration](https://laranode.doitrixtech.co.ke/packages/horizon/configuration) -- Configure Horizon - [Dashboard](https://laranode.doitrixtech.co.ke/packages/horizon/dashboard) -- Dashboard usage # Horizon Configuration Configure Horizon for your environment. ## Configuration File ```typescript // config/horizon.config.ts export default { domain: "", path: "/horizon", environments: { production: { supervisor: { maxProcesses: 10, balance: "auto", workers: { default: { connection: "redis", queue: ["default"], tries: 3, timeout: 60, }, emails: { connection: "redis", queue: ["emails"], tries: 3, timeout: 120, }, }, }, }, local: { supervisor: { maxProcesses: 3, workers: { default: { connection: "redis", queue: ["default"], }, }, }, }, }, }; ``` ## Environment Variables ```dotenv HORIZON_PATH=/horizon ``` ## Supervisor Configuration | Option | Description | | -------------- | ------------------------ | | `maxProcesses` | Maximum worker processes | | `balance` | Load balancing strategy | | `workers` | Worker definitions | ## Worker Configuration | Option | Description | | ------------ | ------------------ | | `connection` | Queue connection | | `queue` | Queues to process | | `tries` | Max retry attempts | | `timeout` | Job timeout | ## Next Steps - [Horizon Overview](https://laranode.doitrixtech.co.ke/packages/horizon) -- Overview - [Dashboard](https://laranode.doitrixtech.co.ke/packages/horizon/dashboard) -- Dashboard usage # Horizon Dashboard The Horizon dashboard provides real-time queue monitoring. ## Accessing the Dashboard Visit `/horizon` in your browser. ## Features - **Queue overview** -- Queue sizes and job counts - **Worker status** -- Running, paused, stopped workers - **Job metrics** -- Throughput, runtime, failures - **Failed jobs** -- View, retry, or forget failed jobs - **Scheduler tasks** -- Scheduled task status ## Managing Workers ```typescript import { HorizonManager } from "@lara-node/horizon"; const manager = new HorizonManager(); // Start worker await manager.startWorker(workerDef); // Pause worker await manager.pauseWorker(workerId); // Resume worker await manager.resumeWorker(workerId); // Stop worker await manager.stopWorker(workerId); ``` ## Queue Management ```typescript // Get queue jobs const jobs = manager.getQueueJobs("default"); // Get queue sizes const sizes = manager.getQueueSizes(); // Purge queue await manager.purgeQueue("default"); ``` ## Failed Jobs ```typescript // Get failed jobs const failed = manager.getFailedJobs(); // Retry failed job await manager.retryFailed(jobId); // Forget failed job await manager.forgetFailed(jobId); // Flush all failed await manager.flushFailed(); ``` ## Next Steps - [Horizon Overview](https://laranode.doitrixtech.co.ke/packages/horizon) -- Overview - [Configuration](https://laranode.doitrixtech.co.ke/packages/horizon/configuration) -- Configure Horizon - [Telescope](https://laranode.doitrixtech.co.ke/packages/telescope) -- Debug dashboard # Mail Package The `@lara-node/mail` package provides a multi-driver email system with fluent Mailable classes. ## Installation ```bash pnpm add @lara-node/mail @lara-node/core nodemailer ``` ## Overview Features include: - **Multiple drivers** -- SMTP, Log, Array, Failover - **Mailable classes** for building emails - **Fluent API** for composing emails - **Queue support** for async sending - **HTML and text** templates ## Quick Start ### Create a Mailable ```typescript import { Mailable } from "@lara-node/mail"; class WelcomeMail extends Mailable { constructor(private user: User) { super(); } build() { return this.to(this.user.email) .subject("Welcome to LaraNode!") .view("emails.welcome", { user: this.user }); } } ``` ### Send Email ```typescript import { Mail, sendMail } from "@lara-node/mail"; // Using facade await Mail.to("user@example.com").send(new WelcomeMail(user)); // Using helper await sendMail(new WelcomeMail(user)); ``` ## Key Exports | Export | Description | | ------------- | ------------------- | | `Mail` | Mail facade | | `MailManager` | Mail manager | | `Mailer` | Fluent mailer | | `Mailable` | Base mailable class | | `sendMail()` | Send helper | | `queueMail()` | Queue helper | ## Configuration ```typescript // config/mail.config.ts export default { driver: process.env.MAIL_DRIVER || "log", from: { address: process.env.MAIL_FROM_ADDRESS, name: process.env.MAIL_FROM_NAME, }, smtp: { host: process.env.MAIL_HOST, port: parseInt(process.env.MAIL_PORT || "587"), secure: false, auth: { user: process.env.MAIL_USERNAME, pass: process.env.MAIL_PASSWORD, }, }, }; ``` ## Next Steps - [Mailables](https://laranode.doitrixtech.co.ke/packages/mail/mailables) -- Building emails - [Mail Drivers](https://laranode.doitrixtech.co.ke/packages/mail/drivers) -- SMTP, Log, Array - [Queued Mail](https://laranode.doitrixtech.co.ke/packages/mail/queued) -- Queue emails # Mail Drivers LaraNode supports multiple mail drivers. ## SMTP Driver Send emails via SMTP server: ```dotenv MAIL_DRIVER=smtp MAIL_HOST=smtp.mailtrap.io MAIL_PORT=2525 MAIL_USERNAME=username MAIL_PASSWORD=password MAIL_FROM_ADDRESS=hello@example.com MAIL_FROM_NAME="LaraNode App" ``` ## Log Driver Write emails to log (for development): ```dotenv MAIL_DRIVER=log ``` ## Array Driver Store emails in memory (for testing): ```dotenv MAIL_DRIVER=array ``` ## Failover Driver Try multiple mailers in order: ```typescript // config/mail.config.ts export default { driver: "failover", failover: { mailers: ["smtp", "log"], }, }; ``` ## Switching Drivers ```typescript import { MailManager } from "@lara-node/mail"; const manager = new MailManager(); // Use specific mailer manager.mailer("smtp").send(mailable); manager.mailer("log").send(mailable); ``` ## Next Steps - [Mailables](https://laranode.doitrixtech.co.ke/packages/mail/mailables) -- Building emails - [Queued Mail](https://laranode.doitrixtech.co.ke/packages/mail/queued) -- Queue emails - [Mail Overview](https://laranode.doitrixtech.co.ke/packages/mail) -- Mail overview # Mailables Mailable classes provide a fluent way to build emails. ## Creating Mailables ```typescript import { Mailable } from "@lara-node/mail"; class WelcomeMail extends Mailable { constructor(private user: User) { super(); } build() { return this.to(this.user.email, this.user.name) .subject("Welcome to LaraNode!") .from("hello@laranode.dev", "LaraNode Team") .text(`Hello ${this.user.name}, welcome!`); } } ``` ## Fluent Methods ```typescript class OrderConfirmationMail extends Mailable { build() { return this.to(this.order.user.email) .cc("admin@example.com") .bcc("archive@example.com") .replyTo("support@example.com") .from("orders@example.com", "Orders") .subject(`Order #${this.order.id} Confirmed`) .text("Your order has been confirmed") .html("

Order Confirmed

") .view("emails.order-confirmation", { order: this.order }) .attach("/path/to/invoice.pdf") .header("X-Custom-Header", "value") .priority("high") .tag("order"); } } ``` ## Simple Mailables ```typescript import { TextMailable, HtmlMailable } from "@lara-node/mail"; // Text only const mail = new TextMailable("user@example.com", "Subject", "Body text"); // HTML only const mail = new HtmlMailable("user@example.com", "Subject", "

Hello

"); ``` ## Attachments ```typescript .build() { return this .to(this.user.email) .subject('Your Report') .attach('/path/to/report.pdf') .attach('/path/to/invoice.pdf', { as: 'invoice.pdf' }) } ``` ## Next Steps - [Mail Overview](https://laranode.doitrixtech.co.ke/packages/mail) -- Mail overview - [Mail Drivers](https://laranode.doitrixtech.co.ke/packages/mail/drivers) -- SMTP, Log, Array - [Queued Mail](https://laranode.doitrixtech.co.ke/packages/mail/queued) -- Queue emails # Queued Mail Send emails asynchronously via the queue system. ## Queueing Emails ```typescript import { queueMail, Mail } from "@lara-node/mail"; // Queue for later await queueMail(new WelcomeMail(user)); // Queue with delay await Mail.to(user.email).later(new WelcomeMail(user), 300); // 5 minutes ``` ## In Mailables ```typescript class WelcomeMail extends Mailable { queue = "emails"; delay = 60; build() { return this.to(this.user.email).subject("Welcome!").text(`Hello ${this.user.name}`); } } ``` ## Queue Connection ```typescript await Mail.mailer("smtp").queue(new WelcomeMail(user)); ``` ## Next Steps - [Mailables](https://laranode.doitrixtech.co.ke/packages/mail/mailables) -- Building emails - [Queue](https://laranode.doitrixtech.co.ke/packages/queue) -- Queue system - [Mail Drivers](https://laranode.doitrixtech.co.ke/packages/mail/drivers) -- Mail drivers # Middlewares Package The `@lara-node/middlewares` package provides pre-built middleware for common use cases. ## Installation ```bash pnpm add @lara-node/middlewares @lara-node/core express ``` ## Overview Includes middleware for: - **Authentication** -- JWT validation - **Authorization** -- Role and permission checks - **Request logging** -- Structured request logs - **Error handling** -- Unified error responses - **Validation** -- Request validation helper - **Response extension** -- Auto-serialize models - **Async context** -- Request-scoped storage ## Quick Start ```typescript import { AuthMiddleware, RequestLoggerMiddleware, ErrorHandlerMiddleware, ValidatorMiddleware, ResponseExtenderMiddleware, } from "@lara-node/middlewares"; export class MiddlewareProvider extends MiddlewareServiceProvider { registerMiddleware() { return { aliases: { auth: AuthMiddleware, logger: RequestLoggerMiddleware, validator: ValidatorMiddleware, errorHandler: ErrorHandlerMiddleware, }, groups: { api: ["errorHandler", "logger", "validator", "auth"], }, priority: ["errorHandler"], }; } } ``` ## Key Exports | Export | Description | | ----------------------------- | ------------------------ | | `AuthMiddleware` | JWT authentication | | `RequestLoggerMiddleware` | Request logging | | `ValidatorMiddleware` | Request validation | | `ResponseExtenderMiddleware` | Auto-serialize models | | `ErrorHandlerMiddleware` | Error handling | | `AuthorizeByStatusMiddleware` | Status check | | `authorizeRoles()` | Role authorization | | `authorizePermissions()` | Permission authorization | | `AsyncContextMiddleware` | AsyncLocalStorage | ## Next Steps - [Built-in Middleware](https://laranode.doitrixtech.co.ke/packages/middlewares/built-in) -- All middleware - [Auth & Authorization](https://laranode.doitrixtech.co.ke/packages/middlewares/auth) -- Auth middleware - [Router Middleware](https://laranode.doitrixtech.co.ke/packages/router/middleware) -- Route middleware # Auth & Authorization Middleware Protect routes with authentication and authorization. ## AuthMiddleware Validates JWT tokens: ```typescript import { AuthMiddleware } from "@lara-node/middlewares"; Route.get("/profile", handler).middleware("auth"); ``` ## AuthorizeByStatusMiddleware Checks user active status: ```typescript import { AuthorizeByStatusMiddleware } from "@lara-node/middlewares"; Route.get("/dashboard", handler).middleware("active"); ``` ## Role Authorization ```typescript import { authorizeRoles } from "@lara-node/middlewares"; Route.get("/admin", handler).middleware(authorizeRoles("admin", "superadmin")); Route.get("/moderator", handler).middleware(authorizeRoles("moderator")); ``` ## Permission Authorization ```typescript import { authorizePermissions } from "@lara-node/middlewares"; Route.post("/posts", handler).middleware(authorizePermissions("posts.create")); Route.put("/posts/:id", handler).middleware(authorizePermissions("posts.update")); Route.delete("/posts/:id", handler).middleware(authorizePermissions("posts.delete")); ``` ## Combining Middleware ```typescript Route.group(() => { Route.get("/users", UserController.index); Route.post("/users", UserController.store); }).middleware(["auth", authorizeRoles("admin"), authorizePermissions("users.manage")]); ``` ## Next Steps - [Built-in Middleware](https://laranode.doitrixtech.co.ke/packages/middlewares/built-in) -- All middleware - [Auth Package](https://laranode.doitrixtech.co.ke/packages/auth) -- Authentication - [Middlewares Overview](https://laranode.doitrixtech.co.ke/packages/middlewares) -- Overview # Built-in Middleware LaraNode provides several pre-built middleware classes. ## AuthMiddleware Validates JWT tokens from the `Authorization` header: ```typescript import { AuthMiddleware } from "@lara-node/middlewares"; // Sets req.user with decoded token payload Route.get("/profile", handler).middleware("auth"); ``` ## RequestLoggerMiddleware Logs each request with details: ```typescript import { RequestLoggerMiddleware } from "@lara-node/middlewares"; // Logs: method, URL, status, duration, IP, user ``` ## ValidatorMiddleware Attaches `req.validate()` method: ```typescript import { ValidatorMiddleware } from "@lara-node/middlewares"; // In route handler const data = req.validate({ name: "required|string", email: "required|email", }); ``` ## ResponseExtenderMiddleware Auto-serializes Model instances in `res.json()`: ```typescript import { ResponseExtenderMiddleware } from "@lara-node/middlewares"; // Automatically calls toJSONAsync() on Model instances res.json(user); // Serialized JSON ``` ## ErrorHandlerMiddleware Handles errors and returns proper responses: ```typescript import { ErrorHandlerMiddleware } from "@lara-node/middlewares"; // Returns 422 for ValidationError // Returns 500 for other errors ``` ## AsyncContextMiddleware Sets up AsyncLocalStorage for request context: ```typescript import { AsyncContextMiddleware, asyncLocalStorage } from "@lara-node/middlewares"; // Access request context anywhere const store = asyncLocalStorage.getStore(); ``` ## Next Steps - [Auth & Authorization](https://laranode.doitrixtech.co.ke/packages/middlewares/auth) -- Auth middleware - [Middlewares Overview](https://laranode.doitrixtech.co.ke/packages/middlewares) -- Overview - [Router Middleware](https://laranode.doitrixtech.co.ke/packages/router/middleware) -- Route middleware # Queue Package The `@lara-node/queue` package provides a job queue system with workers and a task scheduler. ## Installation ```bash pnpm add @lara-node/queue @lara-node/core ``` ## Overview Features include: - **Multiple drivers** -- Sync, Database, Redis - **Job classes** with lifecycle methods - **Queue workers** with daemon mode - **Task scheduler** with cron expressions - **Failed job tracking** and retry - **Job encryption** - **Delays, retries, backoff** ## Quick Start ### Create a Job ```typescript import { Job, Queueable } from "@lara-node/queue"; @Queueable() class SendWelcomeEmail extends Job { constructor(private userId: number) { super(); } async handle() { const user = await User.find(this.userId); await Mail.to(user.email).send(new WelcomeMail(user)); } } ``` ### Dispatch a Job ```typescript // Dispatch to queue await SendWelcomeEmail.dispatch(userId); // Dispatch with options await SendWelcomeEmail.dispatch(userId).onQueue("emails").withDelay(60).withTries(3); ``` ## Key Exports | Export | Description | | ------------------------ | ----------------------- | | `Job` | Base job class | | `@Queueable()` | Job decorator | | `dispatch()` | Dispatch helper | | `PendingDispatch` | Fluent dispatch builder | | `Queue` / `QueueManager` | Queue manager | | `Worker` | Queue worker daemon | | `Schedule` / `scheduler` | Task scheduler | ## Configuration ```typescript // config/queue.config.ts export default { default: process.env.QUEUE_CONNECTION || "sync", connections: { sync: { driver: "sync" }, database: { driver: "database", table: "jobs" }, redis: { driver: "redis", host: process.env.REDIS_HOST, port: parseInt(process.env.REDIS_PORT || "6379"), }, }, }; ``` ## Next Steps - [Jobs](https://laranode.doitrixtech.co.ke/packages/queue/jobs) -- Creating jobs - [Workers](https://laranode.doitrixtech.co.ke/packages/queue/workers) -- Queue workers - [Scheduler](https://laranode.doitrixtech.co.ke/packages/queue/scheduler) -- Task scheduling # Failed Jobs Track and manage failed queue jobs. ## Failed Job Storage Failed jobs are stored in the `failed_jobs` table (database driver) or tracked in Redis. ## Viewing Failed Jobs ```bash # Via Horizon dashboard # Visit /horizon/failed ``` ## Retrying Failed Jobs ```bash # Retry all failed jobs pnpm exec artisan queue:retry all # Retry specific job pnpm exec artisan queue:retry ``` ## Forgetting Failed Jobs ```bash # Forget a failed job pnpm exec artisan queue:forget # Forget all failed jobs pnpm exec artisan queue:flush-failed ``` ## Programmatic Access ```typescript import { Queue } from "@lara-node/queue"; // Get failed jobs const failed = Queue.getFailedJobs(); // Retry failed await Queue.retryFailed(jobId); // Forget failed await Queue.forgetFailed(jobId); // Flush all failed await Queue.flushFailed(); ``` ## Job Failed Callback ```typescript class ProcessPodcast extends Job { async handle() { // Job logic } async failed(error: Error) { // Called when job fails after all retries await Notification.send(admin, "Job failed: " + error.message); } } ``` ## Retry Until Set a time limit for retries: ```typescript class ProcessPodcast extends Job { retryUntil() { return Date.now() + 3600000; // 1 hour from now } } ``` ## Next Steps - [Jobs](https://laranode.doitrixtech.co.ke/packages/queue/jobs) -- Creating jobs - [Workers](https://laranode.doitrixtech.co.ke/packages/queue/workers) -- Queue workers - [Horizon](https://laranode.doitrixtech.co.ke/packages/horizon) -- Queue monitoring # Jobs Jobs are classes that represent units of work to be processed by queue workers. ## Creating Jobs ```typescript import { Job, Queueable } from "@lara-node/queue"; @Queueable() class ProcessPodcast extends Job { constructor(private podcastId: number) { super(); } async handle() { const podcast = await Podcast.find(this.podcastId); // Process the podcast } } ``` ## Job Properties ```typescript @Queueable({ queue: "default", connection: "redis", tries: 3, timeout: 60, backoff: [10, 30, 60], }) class MyJob extends Job { // ... } ``` | Property | Default | Description | | ------------ | ----------- | --------------------- | | `queue` | `'default'` | Queue name | | `connection` | default | Queue connection | | `tries` | 1 | Max retry attempts | | `timeout` | 60 | Timeout in seconds | | `backoff` | `[]` | Delay between retries | | `delay` | 0 | Delay before running | ## Dispatching Jobs ```typescript // Basic dispatch await ProcessPodcast.dispatch(podcastId); // Fluent dispatch await ProcessPodcast.dispatch(podcastId) .onQueue("podcasts") .onConnection("redis") .withDelay(300) .withTries(5) .withTimeout(120) .withBackoff([10, 30, 60]); ``` ## Sync Dispatch Run immediately without queue: ```typescript await ProcessPodcast.dispatchSync(podcastId); ``` ## Dispatch After Response ```typescript await ProcessPodcast.dispatch(podcastId).afterResponse(); ``` ## Job Lifecycle ```typescript class ProcessPodcast extends Job { async handle() { // Main job logic } async failed(error: Error) { // Called when job fails console.error("Job failed:", error.message); } async middleware() { // Middleware to run before job } tags() { // Tags for monitoring return ["podcast", `podcast:${this.podcastId}`]; } } ``` ## Unique Jobs Prevent duplicate jobs: ```typescript @Queueable({ uniqueId: "podcast", uniqueFor: 3600, // 1 hour }) class ProcessPodcast extends Job { // Only one job per podcastId per hour } ``` ## Encrypted Jobs ```typescript @Queueable({ shouldBeEncrypted: true, }) class SensitiveJob extends Job { // Payload is encrypted } ``` ## Next Steps - [Workers](https://laranode.doitrixtech.co.ke/packages/queue/workers) -- Queue workers - [Scheduler](https://laranode.doitrixtech.co.ke/packages/queue/scheduler) -- Task scheduling - [Failed Jobs](https://laranode.doitrixtech.co.ke/packages/queue/failed-jobs) -- Failed job handling # Task Scheduler The scheduler allows you to define cron-like tasks that run automatically. ## Defining Scheduled Tasks ```typescript import { scheduler } from "@lara-node/queue"; // Run a command scheduler.command("cache:clear").daily(); // Run a job scheduler.job(new SendDailyReport()).dailyAt("08:00"); // Run a callback scheduler .call(async () => { await cleanupOldFiles(); }) .hourly(); // Run a shell command scheduler.exec("php artisan backup:run").daily(); ``` ## Schedule Frequencies ```typescript .everyMinute() .everyTwoMinutes() .everyThreeMinutes() .everyFourMinutes() .everyFiveMinutes() .everyTenMinutes() .everyFifteenMinutes() .everyThirtyMinutes() .hourly() .hourlyAt(15) // 15 minutes past the hour .everyTwoHours() .everyThreeHours() .everyFourHours() .everySixHours() .daily() .dailyAt('13:00') .twiceDaily(1, 13) .weekly() .weeklyOn(1, '8:00') // Monday at 8am .monthly() .monthlyOn(4, '15:00') .quarterly() .yearly() .cron('* * * * *') // Custom cron expression ``` ## Constraints ```typescript scheduler .command("report:generate") .daily() .weekdays() // Mon-Fri .weekends() // Sat-Sun .sundays() .mondays() .tuesdays() .wednesdays() .thursdays() .fridays() .saturdays() .between("8:00", "17:00") .unlessBetween("12:00", "13:00") .when(() => isProduction()); ``` ## Running the Scheduler ```bash # Start scheduler (runs in foreground) pnpm exec artisan schedule:run # Or use cron to call this every minute * * * * * cd /path-to-app && pnpm exec artisan schedule:run ``` ## On One Server ```typescript scheduler.command("report:generate").daily().onOneServer(); // Only run on one server ``` ## Run in Background ```typescript scheduler.command("long:task").hourly().runInBackground(); ``` ## Next Steps - [Jobs](https://laranode.doitrixtech.co.ke/packages/queue/jobs) -- Creating jobs - [Workers](https://laranode.doitrixtech.co.ke/packages/queue/workers) -- Queue workers - [Horizon](https://laranode.doitrixtech.co.ke/packages/horizon) -- Queue monitoring # Queue Workers Workers process jobs from the queue. ## Starting a Worker ```bash pnpm exec artisan queue:work ``` ## Worker Options ```bash pnpm exec artisan queue:work --delay=3 --memory=128 --timeout=60 --sleep=3 --tries=3 ``` | Option | Default | Description | | ----------- | ------- | --------------------- | | `--delay` | 0 | Delay between jobs | | `--memory` | 128 | Memory limit (MB) | | `--timeout` | 60 | Job timeout (seconds) | | `--sleep` | 3 | Sleep when no jobs | | `--tries` | 1 | Default max tries | | `--queue` | default | Queue to process | | `--rest` | 0 | Rest between jobs | ## Worker Events ```typescript worker.on("worker:start", () => console.log("Worker started")); worker.on("worker:stop", () => console.log("Worker stopped")); worker.on("worker:pause", () => console.log("Worker paused")); worker.on("worker:resume", () => console.log("Worker resumed")); worker.on("job:processing", (job) => console.log("Processing:", job)); worker.on("job:processed", (job) => console.log("Processed:", job)); worker.on("job:failed", (job, error) => console.log("Failed:", error)); worker.on("job:exception", (job, error) => console.log("Exception:", error)); ``` ## Programmatic Worker ```typescript import { Worker } from "@lara-node/queue"; const worker = new Worker({ delay: 3, memory: 128, timeout: 60, sleep: 3, maxTries: 3, }); await worker.daemon(); ``` ## Control Worker ```typescript await worker.pause(); await worker.resume(); await worker.stop(); ``` ## Restart Worker ```bash pnpm exec artisan queue:restart ``` ## Next Steps - [Jobs](https://laranode.doitrixtech.co.ke/packages/queue/jobs) -- Creating jobs - [Scheduler](https://laranode.doitrixtech.co.ke/packages/queue/scheduler) -- Task scheduling - [Failed Jobs](https://laranode.doitrixtech.co.ke/packages/queue/failed-jobs) -- Failed job handling # Router Package The `@lara-node/router` package provides an expressive routing system with a fluent builder, controller decorators, route model binding, and OpenAPI generation. ## Installation ```bash pnpm add @lara-node/router @lara-node/core express reflect-metadata ``` ## Overview Features include: - **Fluent route builder** with chainable methods - **Controller decorators** for declarative routing - **Route groups** with shared middleware and prefixes - **Route model binding** for automatic model resolution - **Resource routing** for RESTful controllers - **Named routes** for URL generation - **OpenAPI generation** from route metadata - **Rate limiting** middleware ## Quick Start ```typescript import { Route } from "@lara-node/router"; // Basic routes Route.get("/users", UserController.index); Route.post("/users", UserController.store); Route.get("/users/:id", UserController.show); Route.put("/users/:id", UserController.update); Route.delete("/users/:id", UserController.destroy); ``` ## Key Exports | Export | Description | | ------------------ | ------------------------------- | | `RouterBuilder` | Fluent route builder | | `Route` | Route decorator for controllers | | `@Route()` | Class decorator | | `@Route.get()` | Method decorator | | `@Bind()` | Route model binding decorator | | `@Middleware()` | Middleware alias decorator | | `Doc` | OpenAPI documentation decorator | | `OpenApiGenerator` | OpenAPI spec generator | | `HttpKernel` | HTTP kernel base class | ## Next Steps - [Basic Routing](https://laranode.doitrixtech.co.ke/packages/router/basic) -- Define routes - [Route Groups](https://laranode.doitrixtech.co.ke/packages/router/groups) -- Group routes - [Controllers](https://laranode.doitrixtech.co.ke/packages/router/controllers) -- Controller decorators # Basic Routing Define routes using the fluent `Route` builder. ## Basic Routes ```typescript import { Route } from "@lara-node/router"; Route.get("/hello", () => "Hello World"); Route.post("/users", UserController.store); Route.put("/users/:id", UserController.update); Route.patch("/users/:id", UserController.patch); Route.delete("/users/:id", UserController.destroy); Route.options("/users", UserController.options); Route.head("/users", UserController.head); ``` ## Multiple Methods ```typescript Route.match(["GET", "POST"], "/endpoint", handler); Route.any("/endpoint", handler); ``` ## Route Parameters ```typescript Route.get("/users/:id", (req) => { return User.find(req.params.id); }); Route.get("/posts/:postId/comments/:commentId", (req) => { // req.params.postId // req.params.commentId }); ``` ## Named Routes ```typescript Route.get("/users/profile", UserController.profile).name("profile"); // Generate URL const url = Route.route("profile"); // '/users/profile' ``` ## Route Constraints ```typescript Route.get("/users/:id", UserController.show).where("id", "[0-9]+"); Route.get("/posts/:slug", PostController.show).where("slug", "[a-z0-9-]+"); ``` ## Redirect Routes ```typescript Route.redirect("/old-route", "/new-route"); Route.permanentRedirect("/old-route", "/new-route"); ``` ## Fallback Route ```typescript Route.fallback(() => { return "404 - Not Found"; }); ``` ## Next Steps - [Route Groups](https://laranode.doitrixtech.co.ke/packages/router/groups) -- Group routes - [Controllers](https://laranode.doitrixtech.co.ke/packages/router/controllers) -- Controller decorators - [Middleware](https://laranode.doitrixtech.co.ke/packages/router/middleware) -- Route middleware # Controllers Controllers group related request handling logic into a single class using decorators. ## Creating Controllers Use the `@Route()` decorator on a class: ```typescript import { Route } from "@lara-node/router"; @Route("/api/users") class UserController { @Route.get("/") async index() { return User.all(); } @Route.get("/:id") async show(req: Request) { return User.find(req.params.id); } @Route.post("/") async store(req: Request) { return User.create(req.body); } @Route.put("/:id") async update(req: Request) { const user = await User.find(req.params.id); return user.update(req.body); } @Route.delete("/:id") async destroy(req: Request) { const user = await User.find(req.params.id); await user.delete(); return { message: "Deleted" }; } } ``` ## Controller Middleware Apply middleware to the entire controller: ```typescript @Route("/api/admin", "auth", "admin") class AdminController { @Route.get("/users") async index() { return User.all(); } } ``` Or on individual methods: ```typescript @Route("/api/users") class UserController { @Route.get("/", "auth") async profile(req: Request) { return req.user; } @Route.post("/") async store(req: Request) { return User.create(req.body); } } ``` ## Registering Controllers ```typescript // In your route provider import { UserController } from "../Controllers/UserController"; Route.addController(UserController); // Or multiple Route.fromControllers([UserController, PostController]); ``` ## Complete Example with Documentation ```typescript import { Route, Doc } from "@lara-node/router"; @Route("/api/users") class UserController { @Doc({ summary: "List all users", description: "Returns a paginated list of users", tags: ["Users"], responses: { 200: { description: "List of users" }, }, }) @Route.get("/") async index() { return User.paginate(15); } @Doc({ summary: "Create a user", tags: ["Users"], auth: true, }) @Route.post("/") async store(req: Request) { return User.create(req.body); } } ``` ## Next Steps - [Route Model Binding](https://laranode.doitrixtech.co.ke/packages/router/model-binding) -- Automatic model resolution - [Resource Routes](https://laranode.doitrixtech.co.ke/packages/router/resource) -- RESTful routing - [OpenAPI](https://laranode.doitrixtech.co.ke/packages/router/openapi) -- API documentation # Route Groups Group routes with shared attributes like middleware, prefixes, and constraints. ## Basic Groups ```typescript Route.group(() => { Route.get("/users", UserController.index); Route.post("/users", UserController.store); }); ``` ## With Prefix ```typescript Route.group(() => { Route.get("/users", UserController.index); Route.get("/users/:id", UserController.show); }).prefix("/api/v1"); ``` ## With Middleware ```typescript Route.group(() => { Route.get("/users", UserController.index); Route.post("/users", UserController.store); }).middleware("auth"); ``` ## Multiple Middleware ```typescript Route.group(() => { Route.get("/admin", AdminController.index); }).middleware(["auth", "admin"]); ``` ## Chaining ```typescript Route.group(() => { Route.get("/users", UserController.index); Route.post("/users", UserController.store); Route.get("/users/:id", UserController.show); }) .prefix("/api") .middleware("auth") .name("api."); ``` ## Nested Groups ```typescript Route.group(() => { Route.group(() => { Route.get("/users", UserController.index); }) .prefix("/admin") .middleware("admin"); Route.group(() => { Route.get("/users", PublicController.index); }).prefix("/public"); }).prefix("/api/v1"); ``` ## Without Middleware Remove middleware from a group: ```typescript Route.group(() => { Route.get("/public", PublicController.index); }) .middleware("api") .withoutMiddleware("auth"); ``` ## Named Groups ```typescript Route.group(() => { Route.get("/users", UserController.index).name("index"); }) .prefix("/api") .name("api.users."); // Results in route name: api.users.index ``` ## Next Steps - [Controllers](https://laranode.doitrixtech.co.ke/packages/router/controllers) -- Controller decorators - [Middleware](https://laranode.doitrixtech.co.ke/packages/router/middleware) -- Route middleware - [Resource Routes](https://laranode.doitrixtech.co.ke/packages/router/resource) -- RESTful routing # Route Middleware Apply middleware to individual routes or groups. ## Route-Level Middleware ```typescript Route.get("/profile", UserController.profile).middleware("auth"); Route.get("/admin", AdminController.index).middleware(["auth", "admin"]); ``` ## Middleware with Parameters ```typescript Route.get("/api/data", DataController.index).middleware("throttle:60,1"); // 60 requests per minute ``` ## Group Middleware ```typescript Route.group(() => { Route.get("/users", UserController.index); Route.post("/users", UserController.store); }).middleware("api"); ``` ## Without Middleware ```typescript Route.group(() => { Route.get("/public", PublicController.index); }) .middleware("api") .withoutMiddleware("auth"); ``` ## Registering Middleware Aliases ```typescript import { MiddlewareServiceProvider } from "@lara-node/core"; export class MiddlewareProvider extends MiddlewareServiceProvider { registerMiddleware() { return { aliases: { auth: AuthMiddleware, admin: AdminMiddleware, throttle: ThrottleMiddleware, }, groups: { api: ["throttle:60,1", "auth"], web: ["session"], }, }; } } ``` ## Next Steps - [Middleware Guide](https://laranode.doitrixtech.co.ke/guide/middleware) -- Full middleware guide - [Built-in Middleware](https://laranode.doitrixtech.co.ke/packages/middlewares/built-in) -- Pre-built middleware - [Controllers](https://laranode.doitrixtech.co.ke/packages/router/controllers) -- Controller decorators # Route Model Binding Route model binding automatically resolves route parameters to model instances. ## Binding Models Use the `@Bind()` decorator on a model: ```typescript import { Model, Bind } from "@lara-node/db"; @Bind("user") class User extends Model { static table = "users"; } ``` Now routes with `:user` parameter automatically resolve: ```typescript Route.get("/users/:user", (req) => { // req.user is already the User model instance return req.user; }); ``` ## Controller Usage ```typescript @Route("/api/users") class UserController { @Route.get("/:user") async show(req: Request) { // req.user is the resolved User model return req.user; } @Route.put("/:user") async update(req: Request) { const user = req.user; return user.update(req.body); } } ``` ## Custom Resolution You can customize how models are resolved: ```typescript Route.model("user", async (value) => { return User.where("slug", value).firstOrFail(); }); ``` ## Enable Auto Model Binding ```typescript Route.enableAutoModelBinding(); ``` ## Next Steps - [Controllers](https://laranode.doitrixtech.co.ke/packages/router/controllers) -- Controller decorators - [Resource Routes](https://laranode.doitrixtech.co.ke/packages/router/resource) -- RESTful routing - [Models](https://laranode.doitrixtech.co.ke/packages/db/models) -- Working with models # OpenAPI Generation LaraNode can automatically generate OpenAPI 3.0 specifications from your route metadata. ## Documentation Decorator Use the `@Doc()` decorator to document your routes: ```typescript import { Route, Doc } from "@lara-node/router"; @Route("/api/users") class UserController { @Doc({ summary: "List all users", description: "Returns a paginated list of all users", tags: ["Users"], auth: true, params: { page: { type: "integer", description: "Page number" }, perPage: { type: "integer", description: "Items per page" }, }, responses: { 200: { description: "List of users" }, 401: { description: "Unauthenticated" }, }, }) @Route.get("/") async index() { return User.paginate(15); } @Doc({ summary: "Create a user", tags: ["Users"], auth: true, body: { name: { type: "string", required: true }, email: { type: "string", required: true }, password: { type: "string", required: true }, }, responses: { 201: { description: "User created" }, 422: { description: "Validation error" }, }, }) @Route.post("/") async store(req: Request) { return User.create(req.body); } @Doc({ summary: "Get a user", tags: ["Users"], deprecated: true, }) @Route.get("/:id") async show(req: Request) { return User.find(req.params.id); } } ``` ## Doc Options | Option | Type | Description | | ------------- | --------- | ----------------------- | | `summary` | string | Short summary | | `description` | string | Detailed description | | `tags` | string [] | OpenAPI tags | | `auth` | boolean | Requires authentication | | `params` | object | Query parameters | | `body` | object | Request body schema | | `responses` | object | Response definitions | | `deprecated` | boolean | Mark as deprecated | ## Generating the Spec ```typescript import { OpenApiGenerator, RouteScanner } from "@lara-node/router"; const scanner = new RouteScanner(); const routes = scanner.scan(); const generator = new OpenApiGenerator({ title: "My API", version: "1.0.0", description: "API Documentation", }); const spec = generator.generate(routes); ``` ## Serving the Spec ```typescript Route.get("/api/docs.json", () => { return generateOpenApiSpec(); }); ``` ## Next Steps - [Controllers](https://laranode.doitrixtech.co.ke/packages/router/controllers) -- Controller decorators - [Basic Routing](https://laranode.doitrixtech.co.ke/packages/router/basic) -- Define routes - [Route Groups](https://laranode.doitrixtech.co.ke/packages/router/groups) -- Group routes # Resource Routes Resource routes automatically generate RESTful routes for a controller. ## Resource Routing ```typescript Route.resource("users", UserController); ``` This generates: | Method | URI | Action | Route Name | | --------- | ---------------- | ------- | ------------- | | GET | /users | index | users.index | | GET | /users/create | create | users.create | | POST | /users | store | users.store | | GET | /users/\:id | show | users.show | | GET | /users/\:id/edit | edit | users.edit | | PUT/PATCH | /users/\:id | update | users.update | | DELETE | /users/\:id | destroy | users.destroy | ## API Resource For API-only routes (no create/edit forms): ```typescript Route.apiResource("users", UserController); ``` This generates: | Method | URI | Action | | --------- | ----------- | ------- | | GET | /users | index | | POST | /users | store | | GET | /users/\:id | show | | PUT/PATCH | /users/\:id | update | | DELETE | /users/\:id | destroy | ## With Middleware ```typescript Route.resource("users", UserController).middleware("auth"); Route.apiResource("posts", PostController).middleware(["auth", "throttle"]); ``` ## With Prefix ```typescript Route.group(() => { Route.resource("users", UserController); Route.resource("posts", PostController); }).prefix("/api/v1"); ``` ## Multiple Resources ```typescript Route.fromControllers([UserController, PostController, CommentController]); ``` ## Next Steps - [Controllers](https://laranode.doitrixtech.co.ke/packages/router/controllers) -- Controller decorators - [OpenAPI](https://laranode.doitrixtech.co.ke/packages/router/openapi) -- API documentation - [Route Groups](https://laranode.doitrixtech.co.ke/packages/router/groups) -- Group routes # Telescope Package The `@lara-node/telescope` package provides a beautiful debug dashboard for requests, queries, exceptions, and more. ## Installation ```bash pnpm add @lara-node/telescope @lara-node/core express ``` ## Overview Features include: - **Request monitoring** -- Log all HTTP requests - **Query logging** -- Track database queries - **Exception tracking** -- Log errors with stack traces - **Cache monitoring** -- Track cache operations - **Job monitoring** -- Track queue jobs - **Schedule monitoring** -- Track scheduled tasks - **Dark-mode SPA** dashboard ## Quick Start Telescope is automatically registered when you install the `TelescopeServiceProvider`: ```typescript import { TelescopeServiceProvider } from "@lara-node/telescope"; app.register(TelescopeServiceProvider); ``` Visit `/telescope` to access the dashboard. ## Key Exports | Export | Description | | -------------------------- | ----------------- | | `TelescopeStore` | Entry store | | `TelescopeDashboard` | HTTP dashboard | | `QueryWatcher` | Query watcher | | `CacheWatcher` | Cache watcher | | `TelescopeServiceProvider` | Auto-registration | ## Next Steps - [Configuration](https://laranode.doitrixtech.co.ke/packages/telescope/configuration) -- Configure Telescope - [Watchers](https://laranode.doitrixtech.co.ke/packages/telescope/watchers) -- Telescope watchers # Telescope Configuration Configure Telescope for your environment. ## Configuration File ```typescript // config/telescope.config.ts export default { path: "/telescope", enabled: process.env.NODE_ENV !== "production", maxEntries: 1000, pruneHours: 48, }; ``` ## Environment Variables ```dotenv TELESCOPE_MAX_ENTRIES=1000 TELESCOPE_PRUNE_HOURS=48 ``` ## Configuration Options | Option | Default | Description | | ------------ | ----------------- | ------------------ | | `path` | `/telescope` | Dashboard URL | | `enabled` | `true` (non-prod) | Enable/disable | | `maxEntries` | 1000 | Max stored entries | | `pruneHours` | 48 | Prune old entries | ## Disabling in Production ```typescript export default { enabled: process.env.NODE_ENV === "development", }; ``` ## Next Steps - [Telescope Overview](https://laranode.doitrixtech.co.ke/packages/telescope) -- Overview - [Watchers](https://laranode.doitrixtech.co.ke/packages/telescope/watchers) -- Telescope watchers # Telescope Watchers Watchers monitor different aspects of your application. ## Entry Types Telescope records these entry types: | Type | Description | | ----------- | --------------------- | | `request` | HTTP requests | | `exception` | Errors and exceptions | | `query` | Database queries | | `cache` | Cache operations | | `job` | Queue jobs | | `schedule` | Scheduled tasks | | `log` | Log entries | ## QueryWatcher Watches database queries via `db:query` events: ```typescript import { QueryWatcher } from "@lara-node/telescope"; // Automatically logs all queries ``` ## CacheWatcher Watches cache operations: ```typescript import { CacheWatcher } from "@lara-node/telescope"; import { setCacheWatchHook } from "@lara-node/cache"; // Hook is set automatically by TelescopeServiceProvider ``` ## Recording Entries ```typescript import { TelescopeStore } from "@lara-node/telescope"; const store = new TelescopeStore(); store.record("request", { method: "GET", url: "/api/users", status: 200, }); store.record("exception", { message: error.message, stack: error.stack, }); ``` ## Querying Entries ```typescript // Get entries const entries = store.getEntries({ type: "request", tag: "api", limit: 50, }); // Get single entry const entry = store.getEntry(id); // Stats const stats = store.stats(); // Clear store.clear(); ``` ## Metric Buckets ```typescript // Per-minute request metrics const requests = store.getRequestBucket(); const queries = store.getQueryBucket(); ``` ## Next Steps - [Telescope Overview](https://laranode.doitrixtech.co.ke/packages/telescope) -- Overview - [Configuration](https://laranode.doitrixtech.co.ke/packages/telescope/configuration) -- Configure Telescope - [Horizon](https://laranode.doitrixtech.co.ke/packages/horizon) -- Queue monitoring # Validator Package The `@lara-node/validator` package provides a Laravel-inspired validation engine with 50+ rules. ## Installation ```bash pnpm add @lara-node/validator ``` ## Overview Features include: - **50+ validation rules** matching Laravel's API - **Dot-notation** for nested fields - **Wildcard patterns** for arrays - **Custom error messages** - **Type coercion** - **Custom validation rules** ## Quick Start ```typescript import { validate } from "@lara-node/validator"; const data = validate(req.body, { name: "required|string|max:255", email: "required|email|unique:users,email", password: "required|min:8|confirmed", age: "integer|between:18,100", }); ``` ## Key Exports | Export | Description | | ----------------- | ------------------------ | | `validate()` | Main validation function | | `ValidationError` | Error with messages | | `RuleFn` | Custom rule type | | `requiredIf()` | Conditional required | | `fileRule` | File validation | | `mimes()` | MIME type validation | | `maxFileSize()` | File size validation | | `phoneRule` | Phone validation | | `creditCardRule` | Credit card validation | ## Next Steps - [Basic Usage](https://laranode.doitrixtech.co.ke/packages/validator/basic) -- Get started - [Validation Rules](https://laranode.doitrixtech.co.ke/packages/validator/rules) -- All rules - [Custom Rules](https://laranode.doitrixtech.co.ke/packages/validator/custom-rules) -- Custom validation # Basic Validation Learn the basics of validating data with LaraNode's validator. ## validate() Function ```typescript import { validate } from "@lara-node/validator"; const validated = validate(data, rules); ``` Returns validated and coerced data, or throws `ValidationError`. ## Basic Rules ```typescript const data = validate(req.body, { name: "required|string|max:255", email: "required|email", age: "required|integer|min:18", }); ``` ## Rule Syntax Rules are pipe-separated: ```text 'required|string|max:255' ``` Parameters use colons: ```text 'between:18,100' 'in:admin,user,moderator' ``` ## Array Validation ```typescript const data = validate(req.body, { tags: "required|array", "tags.*": "string|max:50", }); ``` ## Nested Objects ```typescript const data = validate(req.body, { user: "required", "user.name": "required|string", "user.email": "required|email", "user.address.street": "required|string", "user.address.city": "required|string", }); ``` ## Handling Errors ```typescript import { ValidationError } from "@lara-node/validator"; try { const data = validate(req.body, rules); } catch (error) { if (error instanceof ValidationError) { // error.errors -- rule codes // error.messages -- human-readable messages return res.status(422).json({ errors: error.messages, }); } } ``` ## Nullable Fields ```typescript const data = validate(req.body, { bio: "nullable|string|max:500", website: "nullable|url", }); ``` ## Sometimes Only validate if the field is present: ```typescript const data = validate(req.body, { email: "sometimes|email", }); ``` ## Next Steps - [Validation Rules](https://laranode.doitrixtech.co.ke/packages/validator/rules) -- All available rules - [Custom Rules](https://laranode.doitrixtech.co.ke/packages/validator/custom-rules) -- Custom validation - [Error Messages](https://laranode.doitrixtech.co.ke/packages/validator/messages) -- Custom messages # Custom Rules Create custom validation rules for your specific needs. ## Custom Rule Functions ```typescript import { validate } from "@lara-node/validator"; const data = validate(req.body, { username: [ "required", "string", (value: any) => { if (!/^[a-z0-9_]+$/.test(value)) { return "Username can only contain lowercase letters, numbers, and underscores"; } return true; }, ], }); ``` ## Rule Function Signature ```typescript type RuleFn = (value: any, payload: any, field: string) => string | boolean; ``` Return `true` if valid, or a string error message if invalid. ## Reusable Custom Rules ```typescript function isStrongPassword(value: any): string | boolean { if (typeof value !== "string") return "Password must be a string"; if (value.length < 8) return "Password must be at least 8 characters"; if (!/[A-Z]/.test(value)) return "Password must contain uppercase letter"; if (!/[0-9]/.test(value)) return "Password must contain a number"; if (!/[!@#$%^&*]/.test(value)) return "Password must contain special character"; return true; } const data = validate(req.body, { password: ["required", isStrongPassword], }); ``` ## Built-in Custom Rules ### requiredIf / requiredUnless ```typescript import { requiredIf, requiredUnless } from "@lara-node/validator"; const data = validate(req.body, { shipping_address: requiredIf("use_shipping", true), company_name: requiredUnless("account_type", "personal"), }); ``` ### File Validation ```typescript import { fileRule, mimes, maxFileSize } from "@lara-node/validator"; const data = validate(req.body, { avatar: [fileRule, mimes("jpg", "png", "gif"), maxFileSize(2)], }); ``` ### Phone Validation ```typescript import { phoneRule } from "@lara-node/validator"; const data = validate(req.body, { phone: ["required", phoneRule], }); ``` ### Credit Card Validation ```typescript import { creditCardRule } from "@lara-node/validator"; const data = validate(req.body, { card_number: ["required", creditCardRule], }); ``` ### Nested Rules ```typescript import { nestedRule, arrayOfObjectsRule } from "@lara-node/validator"; const data = validate(req.body, { address: nestedRule({ street: "required|string", city: "required|string", zip: "required|string|size:5", }), items: arrayOfObjectsRule({ name: "required|string", quantity: "required|integer|min:1", }), }); ``` ## Next Steps - [Validation Rules](https://laranode.doitrixtech.co.ke/packages/validator/rules) -- All rules - [Error Messages](https://laranode.doitrixtech.co.ke/packages/validator/messages) -- Custom messages - [Basic Usage](https://laranode.doitrixtech.co.ke/packages/validator/basic) -- Get started # Error Messages Customize validation error messages. ## Default Messages By default, validation errors include human-readable messages: ```typescript try { validate(data, rules); } catch (error) { console.log(error.messages); // { // name: ['The name field is required'], // email: ['The email must be a valid email address'], // } } ``` ## Custom Messages Pass custom messages as the third argument: ```typescript validate(req.body, rules, { "name.required": "Please enter your name", "email.required": "Email address is required", "email.email": "Please provide a valid email address", "password.min": "Password must be at least 8 characters", }); ``` ## Field-Specific Messages ```typescript validate(req.body, rules, { "name.required": "What should we call you?", "email.unique": "This email is already registered", }); ``` ## Rule-Specific Messages ```typescript validate(req.body, rules, { required: "This field is required", email: "Must be a valid email", "min.string": "Must be at least :min characters", }); ``` ## Error Structure ```typescript interface ValidationError { errors: Record; // Rule codes messages: Record; // Human-readable } ``` ## Usage in Express ```typescript import { validate, ValidationError } from "@lara-node/validator"; Route.post("/users", async (req, res) => { try { const data = validate(req.body, { name: "required|string", email: "required|email|unique:users", }); const user = await User.create(data); return user; } catch (error) { if (error instanceof ValidationError) { return res.status(422).json({ message: "Validation failed", errors: error.messages, }); } throw error; } }); ``` ## Next Steps - [Validation Rules](https://laranode.doitrixtech.co.ke/packages/validator/rules) -- All rules - [Custom Rules](https://laranode.doitrixtech.co.ke/packages/validator/custom-rules) -- Custom validation - [Basic Usage](https://laranode.doitrixtech.co.ke/packages/validator/basic) -- Get started # Validation Rules LaraNode provides 50+ validation rules matching Laravel's API. ## String Rules | Rule | Description | | ------------------- | -------------------- | | `string` | Must be a string | | `max:N` | Maximum length | | `min:N` | Minimum length | | `size:N` | Exact length | | `between:min,max` | Length between range | | `regex:pattern` | Match regex pattern | | `starts_with:value` | Starts with value | | `ends_with:value` | Ends with value | | `contains:value` | Contains value | ## Numeric Rules | Rule | Description | | ----------------- | --------------------- | | `integer` | Must be an integer | | `numeric` | Must be a number | | `min:N` | Minimum value | | `max:N` | Maximum value | | `size:N` | Exact value | | `between:min,max` | Value between range | | `gt:field` | Greater than field | | `gte:field` | Greater than or equal | | `lt:field` | Less than field | | `lte:field` | Less than or equal | ## Type Rules | Rule | Description | | ----------- | ------------------------------ | | `required` | Must be present and not empty | | `nullable` | Can be null | | `sometimes` | Only validate if present | | `present` | Must be present (can be empty) | | `boolean` | Must be boolean | | `array` | Must be an array | | `json` | Must be valid JSON | ## Format Rules | Rule | Description | | -------------------- | ------------------- | | `email` | Valid email address | | `url` | Valid URL | | `uuid` | Valid UUID | | `phone` | Valid phone number | | `credit_card` | Valid credit card | | `date` | Valid date | | `time` | Valid time | | `datetime` | Valid datetime | | `date_format:format` | Match date format | | `timezone` | Valid timezone | ## Date Comparison | Rule | Description | | ---------------------- | --------------- | | `before:date` | Before date | | `before_or_equal:date` | Before or equal | | `after:date` | After date | | `after_or_equal:date` | After or equal | | `date_equals:date` | Equals date | ## Value Rules | Rule | Description | | ----------------- | ------------------------------ | | `in:a,b,c` | Must be in list | | `not_in:a,b,c` | Must not be in list | | `same:field` | Must match field | | `different:field` | Must differ from field | | `accepted` | Must be yes/on/1/true | | `declined` | Must be no/off/0/false | | `confirmed` | Must match field\_confirmation | ## Database Rules | Rule | Description | | --------------------- | -------------------------- | | `exists:table,column` | Must exist in database | | `unique:table,column` | Must be unique in database | ## Conditional Rules | Rule | Description | | ----------------------------- | ---------------------------------- | | `required_if:field,value` | Required if field equals value | | `required_unless:field,value` | Required unless field equals value | | `required_with:fields` | Required if any fields present | | `required_with_all:fields` | Required if all fields present | | `required_without:fields` | Required if any fields missing | | `required_without_all:fields` | Required if all fields missing | ## File Rules | Rule | Description | | ------------------ | ----------------------- | | `file` | Must be a file | | `mimes:jpg,png` | Allowed MIME types | | `max_file_size:mb` | Maximum file size in MB | ## Examples ```typescript { name: 'required|string|max:255', email: 'required|email|unique:users,email', password: 'required|min:8|confirmed', age: 'required|integer|between:18,100', role: 'required|in:admin,user,moderator', website: 'nullable|url', birthday: 'required|date|before:2006-01-01', terms: 'required|accepted', tags: 'required|array', 'tags.*': 'string|max:50', } ``` ## Next Steps - [Basic Usage](https://laranode.doitrixtech.co.ke/packages/validator/basic) -- Get started - [Custom Rules](https://laranode.doitrixtech.co.ke/packages/validator/custom-rules) -- Custom validation - [Error Messages](https://laranode.doitrixtech.co.ke/packages/validator/messages) -- Custom messages # Agent Skills LaraNode includes a comprehensive set of [Agent Skills](https://www.skills.sh/){rel=""nofollow""} — specialized instructions that help AI assistants work with the framework more effectively. Each skill is a `SKILL.md` file containing YAML frontmatter metadata and markdown body with API references, code patterns, and common tasks. ## Available Skills All skills are located in the root `skills/` directory of the LaraNode monorepo: ### Framework & Tooling | Skill Name | File | Description | | --------------------------- | ------------------------------------------- | ------------------------------------------------------------- | | `laranode-framework` | `skills/laranode-framework/SKILL.md` | Framework overview, philosophy, architecture, getting started | | `laranode-create-lara-node` | `skills/laranode-create-lara-node/SKILL.md` | Project scaffolding with `pnpm create laranode` | ### Core Infrastructure | Skill Name | Package | Description | | ---------------------- | -------------------------------------- | --------------------------------------------------------- | | `laranode-core` | `skills/laranode-core/SKILL.md` | IoC container, Application, Service Providers, Config | | `laranode-router` | `skills/laranode-router/SKILL.md` | Routing, controllers, decorators, OpenAPI | | `laranode-middlewares` | `skills/laranode-middlewares/SKILL.md` | Pre-built HTTP middleware (auth, logging, error handling) | | `laranode-console` | `skills/laranode-console/SKILL.md` | Artisan CLI, 40+ commands, custom commands | ### Data & Validation | Skill Name | Package | Description | | -------------------- | ------------------------------------ | ---------------------------------------------------------------------- | | `laranode-db` | `skills/laranode-db/SKILL.md` | Eloquent ORM, models, migrations, query builder, relationships, traits | | `laranode-validator` | `skills/laranode-validator/SKILL.md` | 50+ validation rules, custom rules, dot-notation | | `laranode-cache` | `skills/laranode-cache/SKILL.md` | Multi-driver caching (file, DB, Redis), rate limiting | ### Async & Communication | Skill Name | Package | Description | | ----------------- | --------------------------------- | ------------------------------------------ | | `laranode-queue` | `skills/laranode-queue/SKILL.md` | Job queue, workers, scheduler, failed jobs | | `laranode-events` | `skills/laranode-events/SKILL.md` | Event dispatcher, listeners, broadcasting | | `laranode-mail` | `skills/laranode-mail/SKILL.md` | Multi-driver email, Mailable classes | ### Security | Skill Name | Package | Description | | --------------- | ------------------------------- | ------------------------------------------ | | `laranode-auth` | `skills/laranode-auth/SKILL.md` | JWT auth, bcrypt hashing, token encryption | ### Date/Time | Skill Name | Package | Description | | ----------------- | --------------------------------- | --------------------------------- | | `laranode-carbon` | `skills/laranode-carbon/SKILL.md` | Carbon-inspired date/time library | ### File Export | Skill Name | Package | Description | | ------------------ | ---------------------------------- | ------------------------------------------------------ | | `laranode-exports` | `skills/laranode-exports/SKILL.md` | PDF, Excel & CSV exports | | `laranode-csv` | `skills/laranode-csv/SKILL.md` | CSV generation, parsing, streaming, manipulation | | `laranode-excel` | `skills/laranode-excel/SKILL.md` | Excel .xlsx generation & parsing | | `laranode-pdf` | `skills/laranode-pdf/SKILL.md` | PDF generation via Puppeteer | | `laranode-xml` | `skills/laranode-xml/SKILL.md` | XML building, parsing, serialization, RSS/Atom/sitemap | | `laranode-html` | `skills/laranode-html/SKILL.md` | HTML rendering, templating, minification, sanitization | ### Monitoring | Skill Name | Package | Description | | -------------------- | ------------------------------------ | ------------------------------- | | `laranode-horizon` | `skills/laranode-horizon/SKILL.md` | Queue monitoring dashboard | | `laranode-telescope` | `skills/laranode-telescope/SKILL.md` | Debug & observability dashboard | ## Setting Up Skills ### Using skills.sh Run the [skills.sh](https://www.skills.sh/){rel=""nofollow""} script in the LaraNode repository root: ### Using npx ```bash npx skills add VEN-LANG/vest ``` ## How Skills Work When an AI assistant connected to the LaraNode repository receives a question, it automatically loads the relevant skill based on the `description` field. The skill provides precise, contextual guidance specific to the matched package. ### Example Activations | User Question | Activated Skill | Guidance Provided | | -------------------------------------- | ----------------------------------- | --------------------------------------------------------- | | "How do I set up model relationships?" | `laranode-db` | Eloquent relationship patterns (hasMany, belongsTo, etc.) | | "How do I define API routes?" | `laranode-router` | Route decorators, groups, resource routing | | "Send email on user registration" | `laranode-events` + `laranode-mail` | Event listener + Mailable class patterns | | "Cache database queries" | `laranode-cache` + `laranode-db` | Cache::remember() with query builder | | "Authenticate API requests" | `laranode-auth` | JWT token generation + auth middleware | | "Generate a PDF report" | `laranode-pdf` | PDF generation with Puppeteer options | | "Parse user-uploaded CSV" | `laranode-csv` | CSV.parse(), import concerns, streaming | | "Monitor queue performance" | `laranode-horizon` | Dashboard setup, worker management |