@ecfjs/http
v1.0.0-rc.7
Published
ECF HTTP kernel, router, request/response pipeline, middleware, controllers, and HTTP adapters
Maintainers
Readme
@ecfjs/http
High-Performance HTTP Transport, Routing, Middleware & MVC Engine for ECF (Elegant Core Framework).
Executive Summary
@ecfjs/http is the primary transport layer of the ECF framework. It provides an enterprise HTTP engine with:
- HttpKernel & HttpServer: Modular HTTP kernel handling application bootstrapping, middleware pipelines, and server lifecycle.
- Request & Response Abstractions: Rich input parsing, dot-notation accessors, type coercions, cookie parsing, content negotiation, proxy trusting, streaming, and file downloads.
- Advanced Routing Engine: Radix/Trie-tree path matching, dynamic parameter extraction (
/users/{id}), regex constraints, named routes, route groups, fallbacks, and RESTful resource routing. - Middleware Pipeline: Asynchronous onion-model pipeline supporting global, grouped, named, and terminating middleware hooks (
terminate). - Pluggable Body Parsers: Built-in support for JSON, URL-encoded forms, multipart uploads, plain text, and raw binary buffers with payload size limits.
- MVC Layer: Controllers,
ControllerResolver,ResourceController,FormRequestvalidation integration, and Laravel-styleJsonResourceAPI transformers. - Security & Performance: Built-in rate limiting (
ThrottleRequests), HTTP caching (HttpCache),Gate/Policyauthorization, and HMAC-signed cookie jars. - Multi-Adapter Support: Native Node.js
http/http2, Express, and Fastify adapter normalization. - Testing Harness: Fluent HTTP test suite (
HttpTestCase&TestResponse).
Table of Contents
- Installation
- Quick Start
- Architecture & Request Processing Flow
- 1. HTTP Kernel (
HttpKernel) - 2. HTTP Server (
HttpServer) - 3. Request Engine (
Request)- 3.1 Basic Request Info & Headers
- 3.2 Query Parameters & Route Params
- 3.3 Cookie Extraction
- 3.4 Lazy Asynchronous Body Parsing
- 3.5 Unified Input Management (
all,input,only,except,has,filled) - 3.6 Type Coercion Helpers (
boolean,integer,float,string,array) - 3.7 Method Inspection & Content Negotiation
- 3.8 Network, Security & Proxy Trusting
- 3.9 File Upload Inspection
- 3.10 Schema Validation Integration (
validate)
- 4. Response Engine (
Response) - 5. Routing Subsystem
- 6. Middleware System
- 7. Pluggable Body Parsers
- 8. MVC & API Layer
- 9. Security, Rate Limiting & Auth
- 10. HTTP Testing Harness (
HttpTestCase&TestResponse) - 11. Exception Handling & HTTP Error Hierarchy
- 12. Full End-to-End Enterprise REST API Example
- 13. Troubleshooting & FAQs
Installation
pnpm add @ecfjs/http @ecfjs/core
# or
npm install @ecfjs/http @ecfjs/coreQuick Start
import { Application, Facade } from "@ecfjs/core";
import { HttpServiceProvider, HttpServer, Route } from "@ecfjs/http";
// 1. Initialize Application Container
const app = new Application();
app.register(HttpServiceProvider);
app.boot();
Facade.setApplication(app);
// 2. Define Routes using Static Route Facade
Route.get("/health", (req, res) => {
return res.json({ status: "healthy", timestamp: new Date().toISOString() });
});
Route.get("/users/{id}", (req, res) => {
const id = req.param("id");
return res.json({ id: Number(id), name: "Alice" });
});
// 3. Launch HTTP Server via Application Listen Shortcut
app.listen(3000, () => {
console.log("ECF HTTP Server running on http://localhost:3000");
});Architecture & Request Processing Flow
Raw HTTP Request (IncomingMessage, ServerResponse)
│
▼
HttpServer.listen()
│
▼
HttpKernel.handle()
│
(Bootstrap Application)
│
▼
Construct Request & Response
│
▼
Execute Global Middleware Pipeline
│
▼
Router.match(request)
│
▼
Resolve Route-Level Middleware
│
▼
Execute Route Middleware Pipeline
│
▼
Execute Route Handler
│
▼
Normalize Controller Return Value
│
▼
Send HTTP Response
│
▼
Execute Terminating Middleware (.terminate())1. HTTP Kernel (HttpKernel)
The HttpKernel class (src/HttpKernel.js) orchestrates the request execution pipeline.
1.1 Kernel Constructor & Injection
HttpKernel requires four primary dependencies injected via constructor:
import { HttpKernel } from "@ecfjs/http";
const kernel = new HttpKernel(
router, // Object with match(request) method
bodyParserManager, // Object with parse(request) method
middlewareResolver, // Object with resolve(route) method
exceptionHandler, // Optional object with handle(error, req, res) method
responseContext, // Optional context object passed to Response
app // Optional Application instance
);1.2 Application Bootstrapping (bootstrap)
kernel.bootstrap() guarantees that app.boot() runs exactly once before processing the first request:
await kernel.bootstrap();
console.log(kernel.isBootstrapped); // true1.3 Request Handling (handle)
The main entrypoint for Node.js HTTP servers:
const response = await kernel.handle(rawRequest, rawResponse);- Calls
bootstrap(). - Wraps raw Node objects into
@ecfjs/httpRequestandResponseinstances. - Chains Global Middleware -> Router Match -> Route Middleware -> Route Handler.
- Normalizes return values into standard responses.
- Invokes terminating hooks.
- Catches uncaught exceptions and delegates to
exceptionHandler.
1.4 Global Middleware Stack (use)
Registers middleware functions running on every single HTTP request before route resolution:
kernel.use(async (req, res, next) => {
console.log(`[${req.method}] ${req.path}`);
await next();
});1.5 Automatic Response Normalization (normalizeResponse)
HttpKernel normalizes any return value from a route closure or controller into a standard Response:
| Route Return Type | Behavior | Content-Type Header |
|---|---|---|
| Response instance | Passed through unchanged | Preserved |
| string | Converted via res.html(str) | text/html; charset=utf-8 |
| Buffer | Converted via res.send(buf) | Preserved or auto-detected |
| Object (Plain) | Converted via res.json(obj) | application/json; charset=utf-8 |
| Object with .render() | Calls await obj.render() -> res.html() | text/html; charset=utf-8 |
1.6 Terminating Middleware Execution (terminateMiddleware)
After the response payload is transmitted to the client, HttpKernel checks all executed middleware for a terminate(request, response) method and runs them asynchronously without delaying response delivery:
class AuditMiddleware {
async handle(req, res, next) {
await next();
}
async terminate(req, res) {
// Runs AFTER client receives response!
await logToAuditDatabase(req.path, res.statusCode);
}
}2. HTTP Server (HttpServer)
HttpServer (src/HttpServer.js) wraps Node's native http.createServer.
2.1 Server Lifecycle (listen, close, address)
import { HttpServer } from "@ecfjs/http";
const server = new HttpServer(kernel);
// Start listening
server.listen(3000, "127.0.0.1", () => {
console.log("Listening on 127.0.0.1:3000");
});
console.log(server.listening); // true
console.log(server.address()); // { address: '127.0.0.1', family: 'IPv4', port: 3000 }
// Close server gracefully
server.close(() => {
console.log("Server stopped");
});2.2 Port & Host Guards
- Ports must be integers between
0and65535. Out-of-range ports throwHttpServerError. - Calling
listen()on an already listening server throwsHttpServerError("Server is already listening."). - Calling
close()on a non-listening server throwsHttpServerError("Cannot close a server that is not listening.").
2.3 Fallback Error Handling (handleUncaughtError)
If an error escapes HttpKernel without an exceptionHandler, HttpServer prevents node crash:
- Uncaught
RouteNotFoundError-> Responds with HTTP404 Not Found. - Generic exceptions -> Responds with HTTP
500 Internal Server Error.
3. Request Engine (Request)
Request (src/Request.js) wraps Node's http.IncomingMessage.
3.1 Basic Request Info & Headers
req.method; // "POST"
req.url; // "/api/users?page=2"
req.path; // "/api/users"
req.headers; // Frozen headers object
req.header("content-type"); // "application/json" (Case-insensitive)
req.hasHeader("authorization"); // true/false3.2 Query Parameters & Route Params
// Query string (e.g. ?search=john&sort=asc)
const search = req.query("search", "default_val");
const allQuery = req.query(); // { search: "john", sort: "asc" }
// Route parameters (set by router, e.g. /users/{id})
const id = req.param("id");
const params = req.params; // { id: "42" }3.3 Cookie Extraction
req.cookies; // { session_id: "xyz123" }
req.cookie("session_id"); // "xyz123"
req.hasCookie("session_id"); // true3.4 Lazy Asynchronous Body Parsing
Body parsing is executed lazily on demand when calling await req.body():
const body = await req.body();3.5 Unified Input Management (all, input, only, except, has, filled)
Unified input merges route parameters, query string data, and parsed request body into a single input interface:
// Retrieve merged payload object
const inputs = await req.all();
// Retrieve key with dot-notation lookup support
const email = await req.input("user.email", "[email protected]");
// Pick subset of inputs
const credentials = await req.only("username", "password");
// Exclude sensitive keys
const safeInputs = await req.except("password", "credit_card");
// Input presence checks
await req.has("email"); // true if present and non-null
await req.hasAny("name", "id"); // true if any key is present
await req.filled("username"); // true if present, non-null, and non-empty string/array/object
await req.missing("legacy_id");// true if key is absent3.6 Type Coercion Helpers (boolean, integer, float, string, array)
Safely coerce input variables to exact scalar types:
const isActive = await req.boolean("is_active"); // "true", 1, "yes", "on" -> true
const age = await req.integer("age", 18); // "25" -> 25
const price = await req.float("price", 0.0); // "19.99" -> 19.99
const name = await req.string("name"); // 123 -> "123"
const tags = await req.array("tags"); // "js" -> ["js"], ["a", "b"] -> ["a", "b"]3.7 Method Inspection & Content Negotiation
req.isGet(); // true if method is GET
req.isPost(); // true if method is POST
req.isMethod("PATCH");
req.accepts("json"); // true if Accept header accepts JSON
req.prefers(["json", "html"]);// Returns preferred MIME choice
req.expectsJson(); // true for AJAX / JSON Accept headers
req.ajax(); // true if X-Requested-With === XMLHttpRequest
req.pjax(); // true if X-PJAX header is present
req.prefetch(); // true if Purpose header === prefetch3.8 Network, Security & Proxy Trusting
Configure reverse proxy trust (Nginx, Cloudflare, AWS ALB):
req.setTrustProxy(true);
req.ip; // Evaluates CF-Connecting-IP, X-Forwarded-For, X-Real-IP
req.ips; // Array of proxy IPs in X-Forwarded-For chain
req.protocol; // "https" or "http"
req.secure; // true if TLS or X-Forwarded-Proto === https
req.host; // "api.example.com"
req.origin; // "https://api.example.com"
req.userAgent;// User-Agent string3.9 File Upload Inspection
const files = await req.files();
const avatar = await req.file("avatar"); // Returns UploadedFile descriptor object3.10 Schema Validation Integration (validate)
Runs validation using @ecfjs/validation:
const validated = await req.validate({
name: "required|string|min:3",
email: "required|email",
age: "numeric|min:18"
});
// Throws ValidationException(422) automatically if rules fail!4. Response Engine (Response)
Response (src/Response.js) wraps Node's http.ServerResponse.
4.1 Status & Header Builders
Method-chainable response configuration:
res.status(201)
.header("X-Custom-Header", "Value")
.contentType("json");4.2 Cookie Serialization (cookie, clearCookie)
res.cookie("session_token", "abc123secret", {
maxAge: 3600 * 24, // 1 day in seconds
httpOnly: true,
secure: true,
sameSite: "strict",
path: "/"
});
// Delete cookie
res.clearCookie("session_token");4.3 Cache Control & Security Headers
// Set Cache-Control header directives
res.cacheControl({ public: true, maxAge: 86400, mustRevalidate: true });
// Prevent client/proxy caching completely
res.noCache();
// Set ETag & Last-Modified
res.etag("v1.2.3");
res.lastModified(new Date());
res.vary("Accept-Encoding");4.4 Terminal Response Body Deliveries
These methods serialize payload content and transmit headers to the wire:
res.json({ success: true, data: [] }, 200);
res.html("<h1>Hello World</h1>", 200);
res.text("Plain text content", 200);
res.noContent(204); // Sends empty payload with status 204
res.redirect("/login", 302);4.5 Streams, Files & Download Deliveries
// Stream a Node.js Readable stream directly to client
await res.stream(readableStream);
// Force file download response with Content-Disposition header
await res.download("/path/to/report.pdf", "Monthly_Report.pdf");4.6 Double-Send Safety Protections
Attempting to send headers or body payload twice throws ResponseError("Headers already sent.").
5. Routing Subsystem
5.1 Verb Binding Methods
The Router (src/Router.js) supports all standard HTTP verbs:
import { Route } from "@ecfjs/http";
Route.get("/users", handler);
Route.post("/users", handler);
Route.put("/users/{id}", handler);
Route.patch("/users/{id}", handler);
Route.delete("/users/{id}", handler);
Route.options("/users", handler);
Route.head("/users", handler);
// Match multiple verbs
Route.match(["GET", "POST"], "/submit", handler);
// Match any verb
Route.any("/webhook", handler);5.2 Dynamic Route Parameters & Regex Constraints
Route.get("/posts/{category}/{slug}", (req, res) => {
const category = req.param("category");
const slug = req.param("slug");
return res.json({ category, slug });
}).where("category", "[a-z]+").where("slug", "[a-z0-9-]+");5.3 Named Routes & URL Generation
Route.get("/users/{id}", handler).name("users.show");
// Build URL programmatically using router
const router = app.make("router");
const url = router.url("users.show", { id: 42 }, { page: 1 });
// Result: "/users/42?page=1"5.4 Route Groups & Prefixes
Nest routes under shared prefixes and middleware chains:
Route.group({ prefix: "/api/v1", middleware: ["auth"] }, () => {
Route.get("/profile", profileHandler);
Route.group({ prefix: "/admin", middleware: ["admin"] }, () => {
Route.get("/metrics", metricsHandler);
});
});5.5 RESTful Resource Routes
Automatically generates 7 standard CRUD routes for a resource controller:
Route.resource("photos", "PhotoController");
// Generates:
// GET /photos -> PhotoController@index (photos.index)
// GET /photos/create -> PhotoController@create (photos.create)
// POST /photos -> PhotoController@store (photos.store)
// GET /photos/{id} -> PhotoController@show (photos.show)
// GET /photos/{id}/edit-> PhotoController@edit (photos.edit)
// PUT /photos/{id} -> PhotoController@update (photos.update)
// DELETE /photos/{id} -> PhotoController@destroy (photos.destroy)
// API Resource (Excludes create & edit forms):
Route.apiResource("posts", "PostController");5.6 Fallback Routes
Catches any unmatched incoming request path:
Route.fallback((req, res) => {
return res.status(404).json({ error: "Endpoint not found" });
});5.7 High-Performance Trie Router (TrieRouter)
TrieRouter (src/routing/TrieRouter.js) uses an internal prefix radix-tree (TrieNode) to perform $O(K)$ parameter extraction and path resolution where $K$ is the path segment depth.
5.8 Route Model Binding (ModelBinder)
ModelBinder automatically resolves database models for matching route parameters before invoking controller actions.
6. Middleware System
6.1 Asynchronous Onion Pipeline (Pipeline)
Pipeline (src/Pipeline.js) executes middleware sequentially in onion layers:
import { Pipeline } from "@ecfjs/http";
const result = await new Pipeline()
.send(req, res)
.through([middleware1, middleware2])
.then(async (request, response) => {
return "Final Controller Output";
});6.2 MiddlewareRegistry (Global, Named, Groups)
Register named middleware aliases and groups in container:
import { MiddlewareRegistry } from "@ecfjs/http";
// Named alias
MiddlewareRegistry.alias("auth", AuthMiddleware);
// Middleware group
MiddlewareRegistry.group("web", [
CookieMiddleware,
SessionMiddleware
]);6.3 MiddlewareResolver
Resolves string names (e.g. "auth", "web") into concrete middleware executable pipelines.
6.4 Terminating Middleware Lifecycle
Middleware implementing terminate(request, response) automatically run after response dispatch.
7. Pluggable Body Parsers
BodyParserManager (src/BodyParserManager.js) delegates parsing based on Content-Type:
JsonBodyParser: Parsesapplication/json. ThrowsInvalidJsonErroron malformed JSON.FormBodyParser: Parsesapplication/x-www-form-urlencodedandmultipart/form-data.TextBodyParser: Parsestext/plain.RawBodyParser: Reads raw binaryBuffer.
Maximum body size protection throws PayloadTooLargeError if content exceeds configured limits.
8. MVC & API Layer
8.1 Base Controllers & Resolution
import { Controller } from "@ecfjs/http";
export class UserController extends Controller {
async show(req, res) {
const id = req.param("id");
return res.json({ id, name: "Alice" });
}
}8.2 Form Request Validation
Extend FormRequest (src/validation/FormRequest.js) to decouple validation rules from controller actions:
import { FormRequest } from "@ecfjs/http";
export class StoreUserRequest extends FormRequest {
authorize() {
return true; // Return false to throw ForbiddenException(403)
}
rules() {
return {
name: "required|string|min:3",
email: "required|email"
};
}
}8.3 API Resources & Collections
Transform models into clean JSON responses using JsonResource and ResourceCollection:
import { JsonResource, ResourceCollection } from "@ecfjs/http";
export class UserResource extends JsonResource {
toArray(req) {
return {
id: this.resource.id,
fullName: this.resource.name,
emailAddress: this.resource.email
};
}
}
// In Controller:
return new UserResource(user);
// Or collection:
return new UserResourceCollection(users);9. Security, Rate Limiting & Auth
9.1 Rate Limiting (ThrottleRequests)
Attaches rate limiting to routes. Sets HTTP response headers:
X-RateLimit-LimitX-RateLimit-RemainingRetry-After(when throttled)
Exceeding the rate limit throws RateLimitException (HTTP 429).
9.2 Authorization (Gate & Policy)
Define user permissions:
import { Gate } from "@ecfjs/http";
Gate.define("update-post", (user, post) => {
return user.id === post.userId;
});
if (Gate.denies("update-post", post)) {
throw new ForbiddenException("Unauthorized post edit");
}9.3 CookieJar & Session Storage
CookieJar: Handles encrypted / HMAC-signed cookies.SessionStore: Flash message lifecycle and session attribute management.
10. HTTP Testing Harness (HttpTestCase & TestResponse)
Write expressively fluent integration tests for your HTTP endpoints:
import { HttpTestCase } from "@ecfjs/http";
class UserApiTest extends HttpTestCase {
async testGetUserProfile() {
const response = await this.get("/api/users/1");
response.assertStatus(200)
.assertJson({ id: 1, name: "Alice" })
.assertHeader("content-type", "application/json; charset=utf-8");
}
}11. Exception Handling & HTTP Error Hierarchy
All HTTP exceptions derive from HttpException (src/exceptions/HttpException.js):
HttpException (statusCode, message)
├── BadRequestException (400)
├── UnauthorizedException (401)
├── ForbiddenException (403)
├── NotFoundException (404)
├── MethodNotAllowedException (405)
├── CsrfException (419)
├── ValidationException (422)
├── RateLimitException (429)
├── InternalServerException (500)
└── ServiceUnavailableException (503)12. Full End-to-End Enterprise REST API Example
import { Application, ServiceProvider, Facade } from "@ecfjs/core";
import {
HttpServiceProvider,
HttpServer,
Route,
Controller,
FormRequest,
JsonResource,
MiddlewareRegistry
} from "@ecfjs/http";
// 1. Define Form Request Validator
class CreateProductRequest extends FormRequest {
rules() {
return {
title: "required|string|min:3",
price: "required|numeric|min:0.01"
};
}
}
// 2. API Resource Transformer
class ProductResource extends JsonResource {
toArray(req) {
return {
id: this.resource.id,
title: this.resource.title,
formattedPrice: `$${this.resource.price.toFixed(2)}`
};
}
}
// 3. Product Controller
class ProductController extends Controller {
static db = [
{ id: 1, title: "Laptop", price: 999.99 },
{ id: 2, title: "Mouse", price: 29.99 }
];
async index(req, res) {
return res.json(ProductController.db.map(p => new ProductResource(p).toArray(req)));
}
async store(req, res) {
const validated = await req.validate(new CreateProductRequest().rules());
const product = { id: Date.now(), ...validated };
ProductController.db.push(product);
return res.status(201).json(new ProductResource(product).toArray(req));
}
async show(req, res) {
const id = req.integer("id");
const product = ProductController.db.find(p => p.id === id);
if (!product) {
return res.status(404).json({ error: "Product not found" });
}
return res.json(new ProductResource(product).toArray(req));
}
}
// 4. Application Bootstrap
const app = new Application();
app.register(HttpServiceProvider);
app.boot();
Facade.setApplication(app);
// 5. Register Middleware
MiddlewareRegistry.global(async (req, res, next) => {
res.header("X-Framework", "ECF");
await next();
});
// 6. Define Routes
Route.group({ prefix: "/api/v1" }, () => {
Route.get("/products", [ProductController, "index"]);
Route.post("/products", [ProductController, "store"]);
Route.get("/products/{id}", [ProductController, "show"]);
});
// 7. Start HTTP Server
app.listen(3000, () => {
console.log("Enterprise REST API running on http://localhost:3000");
});13. Troubleshooting & FAQs
1. RouteNotFoundError: Route GET /path not found
- Cause: No route was registered matching the method and URL path.
- Solution: Verify route definition verb and path string, or add a
Route.fallback()handler.
2. ResponseError: Headers already sent.
- Cause: Attempting to invoke
res.json(),res.send(), orres.header()after headers have already been dispatched. - Solution: Ensure your route closures return once after calling a terminal response method.
3. ValidationException (422 Unprocessable Entity)
- Cause: Incoming request payload failed schema validation rules inside
req.validate(). - Solution: Inspect request payload or catch
ValidationExceptioninHttpExceptionHandler.
