UNPKG

@d3vtool/ex-frame

Version:

This library enhances Express.js by providing a more organized structure for web API projects, along with improved control and error handling capabilities.

405 lines (313 loc) 11 kB
# ExFrame **ExFrame** is a lightweight library that streamlines web API development with Express.js by offering improved organization, error handling, middleware management, and enhanced Dependency Injection (DI) capabilities. It simplifies controller-based routing, boosts maintainability, and promotes best practices for scalable applications. By tightly integrating with Express.js, ExFrame ensures cleaner code and better separation of concerns. --- ## 📌 Installation ```sh npm install @d3vtool/ex-frame ``` --- - **Contents** - [**Getting Started**](#-getting-started) - [**Dependency Injection**](#-dependency-injection) - [**Ex-Frame Utilities**](#ex-frame-utilities) --- ## 🚀 Getting Started ### 1️⃣ Setting Up ### ✅ Prerequisites Before using ExFrame, ensure the following: 1. TypeScript is being used. 2. The following options are enabled in `tsconfig.json`: ```json { "compilerOptions": { "experimentalDecorators": true, "emitDecoratorMetadata": true } } ``` --- ### ✅ Basic Setup Create an entry point (`index.ts`) and initialize the framework: ```typescript import { ExFrame } from "@d3vtool/ex-frame"; import express from "express"; import { UsersController } from "./src/controllers/UsersController"; const app = express(); const frame = new ExFrame(app); frame.controller(UsersController); // Registers the controller frame.listen(3000, () => { console.log("Server is running on port 3000"); }); ``` --- ## ✅ Dependency Injection Create an entry point (`index.ts`), initialize the framework, and configure the Dependency Injection (DI) system: ```typescript import express, { type Request, type Response } from "express"; import { ExFrame, Get, InjectScoped } from "@d3vtool/ex-frame"; const app = express(); const frame = new ExFrame(app); // (test-service.ts) Define Services with different lifetimes class TestService { doesWork() { console.log("it works in test service..."); } } frame.addTransient(TestService); // Adds TestService as a Transient service // (user-service.ts) class UserService { getUser(id: string) { console.log(`fetching data for ${id}...`); } } frame.addScoped(UserService); // Adds UserService as a Scoped service // (db-service.ts) class DBService { static query(stmt: string) { console.log(`query ${stmt}...`); } } frame.addSingleton(DBService); // Adds DBService as a Singleton service // Controller with Dependency Injection class UserController { @Get("/") @InjectScoped() // scoped lifetime static async getUser( // [ the variable name should be same as configuring Class name. ] userService: UserService, req: Request, res: Response ) { const user = await userService.getUser(req.params.id); res.send({ status: "success", user }); } } frame.controller(UserController); // Registers the controller // Start the server frame.listen(3000, () => { console.log("Server is running on port 3000"); }); ``` ### 🛠️ Key Concepts: - **Transient Service:** A new instance of `TestService` is created each time it is requested. - **Scoped Service:** A single instance of `UserService` is shared within the request's scope. - **Singleton Service:** `DBService` is a shared instance across the entire application. ### 🔄 Dependency Injection: - The `@InjectTransient()` decorator automatically injects `TestService` as a transient dependency. - The `@InjectScoped()` decorator automatically injects `UserService` as a transient dependency. - The `@InjectSingleton()` decorator automatically injects `DBService` as a transient dependency. - The `frame.addTransient()`, `frame.addScoped()`, and `frame.addSingleton()` methods configure the DI container with the appropriate service lifetime. --- ## 📌 Controllers and Decorators ### 2️⃣ Creating a Controller 1. A controller defines the logic for handling incoming requests. Decorators help in structuring routes, managing middleware, and handling errors. 2. Controller methods must be declared as static. ### ✅ Example ```typescript export class UsersController { @Get("/") @Middlewares(authMiddleware) static getAll(req: Request, res: Response) { res.send("Hello"); } } ``` --- ### 3️⃣ **@ParentRoute** (Class Decorator) Defines a parent route for all methods inside a controller. ```typescript @ParentRoute("/users") // applied to this controller only export class UsersController { // All methods inside this class will be prefixed with "/users" } ``` 📌 **Notes** - All methods inside the class will be grouped under `/users`. - Methods inside the controller must be **static**. --- ### 4️⃣ **@ErrorHandler** (Class | Method Decorator) Handles errors within a specific route or acts as a fallback if an explicit error handler is not set. ```typescript /* It will also act as a fallback if some method throws error and you didn't declare '@ErrorHandler' over that method, then this will catch it. */ @ErrorHandler(controllerErrorHandler) // applied to this controller only export class UsersController { @Get("/:id") @ErrorHandler(routeErrorHandler) // Can be applied to specific methods static getUser(req: Request, res: Response) { throw new Error("Something went wrong"); } } ``` 📌 **Notes** - If an error occurs inside `getUser`, the method-level `@ErrorHandler` will handle it. - If no method-level error handler is defined, the class-level handler will take over. --- ### 5️⃣ **@Middlewares** (Class | Method Decorator) Registers middleware functions for a specific route. ```typescript // this will run for before any controller method execute. @Middlewares(userControllerMiddleware) export class UsersController { @Get("/:id") @Middlewares(authMiddleware) // Middleware for this specific route static getUser(req: Request, res: Response) { res.send("User details"); } } ``` 📌 **Notes** - Middleware functions are executed before the route handler. - Multiple middleware functions can be applied. --- ### 6️⃣ **Route Method Decorators (@Get, @Post, etc.)** Defines routes for handling different HTTP methods. ```typescript @Get("/:id") static getUser(req: Request, res: Response) { res.send("User details"); } ``` 📌 **Notes** - Decorators like `@Get`, `@Post`, `@Put`, and `@Delete` associate methods with specific HTTP methods. --- ### 7️⃣ **Static Methods in Controllers** All controller methods must be static. ```typescript export class UsersController { @Get("/:id") static getUser(req: Request, res: Response) { res.send("User details"); } } ``` 📌 **Notes** - Static methods ensure proper route registration and execution. --- ## **Ex-Frame Utilities** ### ✅ `@Memoize` Caches the result of a function call, preventing unnecessary recomputation. #### Example: ```typescript import { Memoize } from "@d3vtool/ex-frame"; class UserService { @Memoize() static getUser(id: number) { console.log("Fetching user from DB..."); return { id, name: "John Doe" }; } } console.log(UserService.getUser(1)); // Fetches from DB console.log(UserService.getUser(1)); // Returns cached result ``` --- ### ✅ `@Cache(ttl: number)` Caches the result of a function for a specified time-to-live (TTL). #### Example: ```typescript import { Cache } from "@d3vtool/ex-frame"; class DataService { @Cache(5000) // Cache for 5 seconds static fetchData() { console.log("Fetching data..."); return "data"; } } console.log(DataService.fetchData()); // Fetches new data setTimeout(() => console.log(DataService.fetchData()), 2000); // Returns cached data setTimeout(() => console.log(DataService.fetchData()), 6000); // Fetches fresh data ``` --- ### ✅ `@OnError(fn: (error: unknown) => void)` Catches errors from a function and handles them using the provided error handler. #### Example: ```typescript import { OnError } from "@d3vtool/ex-frame"; function logError(error: unknown) { console.error("Caught error:", error); } class ExampleService { @OnError(logError) static riskyOperation() { throw new Error("Something went wrong!"); } } ExampleService.riskyOperation(); // Logs error instead of crashing ``` --- ### ✅ `@Pipe<T>(fn: (arg: T) => void)` Passes the return value of a function to the provided callback. #### Example: ```typescript import { Pipe } from "@d3vtool/ex-frame"; function logOutput(data: number) { console.log("Processed data:", data); } class UsersController { @Pipe<number>(logOutput) static getUserId(username: string) { return 1001; } } UsersController.getUserId("Alice"); // Logs: "Processed data: 1001" ``` --- ### ✅ `@MeasureTime(fn?: (time: number) => void)` Measures execution time and optionally reports it, else it will log to console. #### Example: ```typescript import { MeasureTime } from "@d3vtool/ex-frame"; function reportTime(ms: number) { console.log(`Execution time: ${ms}ms`); } class PerformanceService { @MeasureTime(reportTime) static compute() { for (let i = 0; i < 1e6; i++); // Simulate work } } PerformanceService.compute(); // Logs execution time ``` --- ### ✅ `@Throttle(limit: number)` Limits function execution to at most once per specified time interval. #### Example: ```typescript import { Throttle } from "@d3vtool/ex-frame"; class ClickHandler { @Throttle(2000) // Allow execution every 2 seconds static handleClick() { console.log("Button clicked!"); } } // Simulate rapid clicks ClickHandler.handleClick(); ClickHandler.handleClick(); // Ignored setTimeout(() => ClickHandler.handleClick(), 2500); // Executed ``` --- ### ✅ `@Debounce(delay: number, fn?: (data: T) => void)` Ensures that a function is executed only after a specified delay, preventing excessive calls. #### Example: ```typescript import { Debounce } from "@d3vtool/ex-frame"; function logSearch(data: string) { console.log("Searching for:", data); } class SearchBar { @Debounce(500, logSearch) // Wait 500ms before execution static search(query: string) { return query; } } // Simulate rapid typing SearchBar.search("A"); SearchBar.search("Ap"); SearchBar.search("App"); // Executes only once after 500ms ```