@d3vtool/ex-frame
TypeScript icon, indicating that this package has built-in type declarations

1.2.4 • Public • Published

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

npm install @d3vtool/ex-frame


🚀 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:
    {
      "compilerOptions": {
        "experimentalDecorators": true,
        "emitDecoratorMetadata": true
      }
    }

✅ Basic Setup

Create an entry point (index.ts) and initialize the framework:

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:

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

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.

@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.

/* 
 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.

// 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.

@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.

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:

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:

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:

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:

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:

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:

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:

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

Package Sidebar

Install

npm i @d3vtool/ex-frame

Weekly Downloads

19

Version

1.2.4

License

MIT

Unpacked Size

40.7 kB

Total Files

17

Last publish

Collaborators

  • ks961