Package Exports
- @d3vtool/ex-frame
- @d3vtool/ex-frame/dist/cjs/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@d3vtool/ex-frame) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
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:
- TypeScript is being used.
- 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 injectsTestService
as a transient dependency. - The
@InjectScoped()
decorator automatically injectsUserService
as a transient dependency. - The
@InjectSingleton()
decorator automatically injectsDBService
as a transient dependency. - The
frame.addTransient()
,frame.addScoped()
, andframe.addSingleton()
methods configure the DI container with the appropriate service lifetime.
📌 Controllers and Decorators
2️⃣ Creating a Controller
A controller defines the logic for handling incoming requests. Decorators help in structuring routes, managing middleware, and handling errors.
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