Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork0
High-performance HTTP framework
License
avoidwork/woodland
Folders and files
| Name | Name | Last commit message | Last commit date | |
|---|---|---|---|---|
Repository files navigation
- 🏆 Performance Leader:15% faster than raw Node.js, 3% faster than Express.js, 87% of Fastify's performance - proven by benchmarks
- ⚡ Zero Overhead: Framework features with performance gains, not costs
- 🔒 Security First: Built-in CORS, ETags, and comprehensive security headers
- 🛤️ Smart Routing: Parameter syntax (
/users/:id) and RegExp support with caching - 🔧 Express Compatible: Familiar middleware with
req, res, nextpattern - 📁 File Serving: High-performance static file server with streaming
- 📘 TypeScript Ready: Full TypeScript definitions included
- 📊 Production Logging: Common Log Format with customizable levels
- 🚀 Modern Architecture: ES6+ modules optimized for Node.js 17+
Woodland follows asecurity-first design philosophy with strong adherence to OWASP guidelines:
- ✅ Injection Prevention: Comprehensive input validation, HTML escaping, and path traversal protection
- ✅ Secure Defaults: CORS disabled by default, autoindex disabled, secure error handling
- ✅ Access Control: Strict file access controls and allowlist-based CORS validation
- ✅ XSS Protection: All user input properly escaped, security headers included
- 🛡️ Security Headers:
X-Content-Type-Options: nosniffset automatically,helmetrecommended for comprehensive headers - ⚡ Rate Limiting: Built for middleware compatibility - use
express-rate-limitor similar - 🔍 Comprehensive Testing: 100+ dedicated security tests covering attack vectors and edge cases
OWASP Top 10 Coverage: Excellent protection against injection attacks, broken access control, security misconfigurations, and cross-site scripting. SeeTechnical Documentation for complete assessment.
💡 Quick Security Setup: Add essential security middleware for production deployment:
importhelmetfrom'helmet';importrateLimitfrom'express-rate-limit';// Security headersapp.always(helmet());// Rate limitingapp.always(rateLimit({windowMs:15*60*1000,// 15 minutesmax:100// limit each IP to 100 requests per windowMs}));
Stop accepting framework overhead. Most HTTP frameworks slow you down in exchange for convenience. Woodland breaks that trade-off.
🏆Proven Performance: Comprehensive benchmarks show Woodlandoutperforms raw Node.js by 15%, Express.js by 3%, and delivers 87% of Fastify's performance
⚡Zero Compromise: Get all the framework features you need with better performance than hand-coding
🚀Battle-Tested: 100% statement coverage with 416 comprehensive tests, production-ready security, and enterprise-grade reliability
🔧Developer Experience: Express-compatible API means zero learning curve for your team
The Result? Your applications run faster, your servers handle more traffic, and your infrastructure costs less.
# npmnpm install woodland# yarnyarn add woodland# pnpmpnpm add woodland# Global installation for CLInpm install -g woodland
import{createServer}from"node:http";import{woodland}from"woodland";constapp=woodland({defaultHeaders:{"cache-control":"public, max-age=3600","content-type":"text/plain"},time:true});app.get("/",(req,res)=>{res.send("Hello World!");});app.get("/users/:id",(req,res)=>{res.json({id:req.params.id,name:`User${req.params.id}`});});createServer(app.route).listen(3000,()=>{console.log("Server running on http://localhost:3000");});
import{Woodland}from"woodland";classMyAPIextendsWoodland{constructor(){super({defaultHeaders:{"x-api-version":"1.0.0"},origins:["https://myapp.com"]});this.setupRoutes();}setupRoutes(){this.get("/api/health",this.healthCheck);this.post("/api/users",this.createUser);}healthCheck(req,res){res.json({status:"healthy",timestamp:newDate().toISOString()});}createUser(req,res){// Handle user creationres.status(201).json({message:"User created"});}}constapi=newMyAPI();
- Configuration
- Routing
- Middleware
- Static Files
- CORS
- Error Handling
- Response Helpers
- Event Handlers
- Logging
- CLI Usage
- API Reference
- Performance
- Testing
- TypeScript
- Examples
- Troubleshooting
constapp=woodland({autoindex:false,// Enable directory browsingcacheSize:1000,// Internal cache sizecacheTTL:10000,// Cache TTL (10 seconds)charset:"utf-8",// Default charsetcorsExpose:"",// CORS exposed headersdefaultHeaders:{},// Default response headersdigit:3,// Timing precision digitsetags:true,// Enable ETag generationindexes:[// Index file names"index.htm","index.html"],logging:{enabled:true,// Enable loggingformat:"%h %l %u %t \"%r\" %>s %b",// Log formatlevel:"info"// Log level},origins:[],// CORS origins (empty array denies all cross-origin requests)silent:false,// Disable default headerstime:false// Enable response timing});
constapp=woodland({// Security headersdefaultHeaders:{"x-content-type-options":"nosniff","x-frame-options":"DENY","x-xss-protection":"1; mode=block","strict-transport-security":"max-age=31536000; includeSubDomains"},// CORS configurationorigins:["https://myapp.com","https://api.myapp.com"],corsExpose:"x-custom-header,x-request-id",// Performance tuningcacheSize:5000,cacheTTL:600000,// 10 minutes// Detailed logginglogging:{enabled:true,level:"debug",format:"%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""},// Enable featurestime:true,etags:true,autoindex:true});
// HTTP methodsapp.get("/users",getAllUsers);app.post("/users",createUser);app.put("/users/:id",updateUser);app.delete("/users/:id",deleteUser);app.patch("/users/:id",patchUser);app.options("/users",optionsHandler);// Route parametersapp.get("/users/:id",(req,res)=>{constuserId=req.params.id;res.json({id:userId});});// Multiple parametersapp.get("/users/:userId/posts/:postId",(req,res)=>{const{userId, postId}=req.params;res.json({userId, postId});});
// RegExp patternsapp.get("/api/v[1-3]/users",(req,res)=>{res.json({version:req.url.match(/v(\d)/)[1]});});// Wildcard routesapp.get("/files/(.*)",(req,res)=>{// Serve any file under /files/});// Route with validationapp.get("/users/:id(\\d+)",(req,res)=>{// Only matches numeric IDsres.json({id:parseInt(req.params.id)});});
// API v1 routesconstapiV1=(req,res,next)=>{req.version="v1";next();};app.get("/api/v1/users",apiV1,getAllUsers);app.post("/api/v1/users",apiV1,createUser);// Protected routesconstauthenticate=(req,res,next)=>{consttoken=req.headers.authorization;if(!token){returnres.error(401);}// Verify token...next();};app.get("/admin/*",authenticate,adminHandler);
// Global middlewareapp.always((req,res,next)=>{console.log(`${req.method}${req.url}`);next();});// Route-specific middlewareapp.get("/protected",authenticate,authorize,handler);// ❌ WRONG: Do NOT register error middleware with 'always'// This will execute BEFORE route handlers, not after errors occurapp.always((error,req,res,next)=>{if(error){console.error(error);res.error(500);}else{next();}});// ✅ CORRECT: Register error middleware with specific routes LASTapp.get("/api/users",authenticate,// Normal middlewareauthorize,// Normal middlewaregetUserHandler,// Route handler(error,req,res,next)=>{// Error middleware - LASTif(error){console.error(error);res.error(500);}else{next();}});// ✅ CORRECT: Global error handling should be done with route patternsapp.use("/(.*)",(error,req,res,next)=>{if(error){console.error(`Global error for${req.url}:`,error);res.error(500,"Internal Server Error");}else{next();}});
Important Notes:
- Error middleware (functions with 4 parameters:
error, req, res, next) shouldnever be registered withapp.always() - Error middleware registered with
alwayswill executebefore route handlers, making them ineffective for catching route errors .use()without a method defaults to GET - it behaves like.get(), not like.always()- Always register error middlewarelast in the middleware chain for each route
- For global error handling, use
app.use("/(.*)", errorHandler)as thelast route registration
// Request loggingconstrequestLogger=(req,res,next)=>{conststart=Date.now();res.on("finish",()=>{constduration=Date.now()-start;console.log(`${req.method}${req.url} -${res.statusCode} (${duration}ms)`);});next();};// Body parserconstbodyParser=async(req,res,next)=>{if(req.method==="POST"||req.method==="PUT"){letbody="";req.on("data",chunk=>body+=chunk);req.on("end",()=>{try{req.body=JSON.parse(body);}catch(e){req.body=body;}next();});}else{next();}};// Rate limitingconstrateLimit=(()=>{constrequests=newMap();return(req,res,next)=>{constip=req.ip;constnow=Date.now();constwindow=60000;// 1 minuteconstlimit=100;if(!requests.has(ip)){requests.set(ip,[]);}constipRequests=requests.get(ip);constrecentRequests=ipRequests.filter(time=>now-time<window);if(recentRequests.length>=limit){returnres.error(429);}recentRequests.push(now);requests.set(ip,recentRequests);next();};})();
// Serve files from public directoryapp.files("/static","./public");// Serve with custom optionsapp.get("/downloads/(.*)",(req,res)=>{constfilename=req.params[0];constfilepath=path.join("./downloads",filename);// Custom file serving logicapp.serve(req,res,filename,"./downloads");});
constapp=woodland({autoindex:true,// Enable directory browsingindexes:["index.html","index.htm","default.html"]});app.files("/","./public");
Woodland handles CORS automatically when you configure origins. Here's what you get for free:
constapp=woodland({origins:["https://myapp.com","https://api.myapp.com"],corsExpose:"x-total-count,x-page-count"});// Woodland automatically provides:// ✅ Preflight OPTIONS route for all paths// ✅ Access-Control-Allow-Origin header (set to request origin if allowed)// ✅ Access-Control-Allow-Credentials: true// ✅ Access-Control-Allow-Methods (based on registered routes)// ✅ Access-Control-Allow-Headers (for OPTIONS requests)// ✅ Access-Control-Expose-Headers (for non-OPTIONS requests)// ✅ Timing-Allow-Origin header// ✅ Origin validation and security
- Preflight Route Registration: When origins are configured, Woodland automatically registers an OPTIONS handler that responds with 204 No Content
- CORS Headers: For valid cross-origin requests, automatically sets all required CORS headers
- Origin Validation: Checks request origin against configured allowed origins
- Method Detection: Access-Control-Allow-Methods reflects actual registered routes
- Security: Empty origins array denies all CORS requests by default
// Conditional CORS (disable automatic CORS, use manual)constapp=woodland({origins:[]// Empty = no automatic CORS});// Dynamic origin validation (replaces automatic validation)app.always((req,res,next)=>{constorigin=req.headers.origin;// Custom logic for origin validationif(isValidOriginForUser(origin,req.user)){res.header("access-control-allow-origin",origin);}next();});app.always((req,res,next)=>{if(shouldAllowCORS(req)){res.header("access-control-allow-origin",req.headers.origin);res.header("access-control-allow-credentials","true");}next();});// Override automatic behavior for specific routesapp.options("/api/special",(req,res)=>{res.header("access-control-allow-methods","GET,POST");// Restrict methodsres.header("access-control-allow-headers","content-type");// Restrict headersres.header("access-control-max-age","86400");// Set cache durationres.send("");});
Most applications only need to configureorigins andcorsExpose - manual CORS handling is rarely necessary.
app.get("/error",(req,res)=>{res.error(500,"Internal Server Error");});app.get("/not-found",(req,res)=>{res.error(404);});app.get("/custom-error",(req,res)=>{res.error(400,"Bad Request",{"content-type":"application/json"});});
app.on("error",(req,res,err)=>{console.error(`Error${res.statusCode}:${err}`);// Log to external serviceif(res.statusCode>=500){logError(err,req);}});// Global error catchingapp.always((req,res,next)=>{try{next();}catch(error){res.error(500,error.message);}});
app.get("/users/:id",(req,res)=>{constuser={id:req.params.id,name:"John Doe"};res.json(user);});app.post("/users",(req,res)=>{constuser=createUser(req.body);res.json(user,201);});
app.get("/old-path",(req,res)=>{res.redirect("/new-path");});app.get("/temporary",(req,res)=>{res.redirect("/permanent",false);// Temporary redirect});
app.get("/api/data",(req,res)=>{res.header("x-total-count","100");res.header("x-page","1");res.json({data:[]});});app.get("/download",(req,res)=>{res.set({"content-disposition":"attachment; filename=data.json","content-type":"application/json"});res.send(JSON.stringify({data:"example"}));});
// Connection establishedapp.on("connect",(req,res)=>{console.log(`Connection from${req.ip}`);});// Request finishedapp.on("finish",(req,res)=>{console.log(`Request completed:${req.method}${req.url}`);});// Error occurredapp.on("error",(req,res,err)=>{console.error(`Error:${err}`);});// File streamingapp.on("stream",(req,res)=>{console.log(`Streaming file to${req.ip}`);});
app.on("finish",(req,res)=>{constmetrics={method:req.method,url:req.url,status:res.statusCode,ip:req.ip,userAgent:req.headers["user-agent"],timestamp:newDate().toISOString()};// Send to analytics serviceanalytics.track(metrics);});
error: Error messageswarn: Warning messagesinfo: Informational messagesdebug: Debug messages
constapp=woodland({logging:{enabled:true,level:"debug",format:"%h %l %u %t \"%r\" %>s %b %D"}});// Manual loggingapp.log("Custom message","info");app.log("Debug information","debug");
| Placeholder | Description |
|---|---|
%h | Remote IP address |
%l | Remote logname (always-) |
%u | Remote user (always-) |
%t | Timestamp |
%r | First line of request |
%s | Status code |
%b | Response size |
%{Header}i | Request header |
%{Header}o | Response header |
# Serve current directorywoodland# Custom IP and portwoodland --ip=0.0.0.0 --port=3000# Disable loggingwoodland --logging=false# Serve specific directorycd /path/to/files&& woodland
The CLI achieves100% test coverage with comprehensive unit tests covering argument parsing, validation, server configuration, error handling scenarios, and actual HTTP request serving verification.
| Option | Default | Description |
|---|---|---|
--ip | 127.0.0.1 | Server IP address |
--port | 8000 | Server port |
--logging | true | Enable/disable request logging |
$ woodland --port=3000id=woodland, hostname=localhost, ip=127.0.0.1, port=3000127.0.0.1 - [18/Dec/2024:10:30:00 -0500]"GET / HTTP/1.1" 200 1327127.0.0.1 - [18/Dec/2024:10:30:05 -0500]"GET /favicon.ico HTTP/1.1" 404 9
newWoodland(config)
get(path, ...middleware)- GET requestspost(path, ...middleware)- POST requestsput(path, ...middleware)- PUT requestsdelete(path, ...middleware)- DELETE requestspatch(path, ...middleware)- PATCH requestsoptions(path, ...middleware)- OPTIONS requeststrace(path, ...middleware)- TRACE requestsconnect(path, ...middleware)- CONNECT requests
always(path, ...middleware)- All HTTP methods - executes before request HTTP method middlewareuse(path, ...middleware, method)- Generic middleware registrationfiles(root, folder)- Static file servingignore(fn)- Ignore middleware forAllowheaderallowed(method, uri)- Check if method is allowedallows(uri)- Get allowed methods stringlist(method, type)- List registered routeslog(message, level)- Log a message
onReady(req, res, body, status, headers)- Before sending responseonSend(req, res, body, status, headers)- Customize responseonDone(req, res, body, headers)- Finalize response
req.allow- Allowed methods for current pathreq.body- Request body (populate with middleware)req.cors- Boolean indicating CORS requestreq.corsHost- Boolean indicating "origin" and "host" request headers are in syncreq.host- Request hostnamereq.ip- Client IP addressreq.params- Route parametersreq.parsed- Parsed URL objectreq.valid- Request validation statusreq.exit()- Exit middleware chain
res.locals- Local variables objectres.error(status, body, headers)- Send error responseres.header(key, value)- Set response headerres.json(body, status, headers)- Send JSON responseres.redirect(url, permanent)- Send redirect responseres.send(body, status, headers)- Send responseres.set(headers)- Set multiple headersres.status(code)- Set status code
Competitive Performance: Woodland delivers excellent performance that outperforms both raw Node.js and Express.js, while achieving 87% of Fastify's performance - a strong showing for a framework that prioritizes developer experience alongside speed.
Framework Comparison (JSON Response) - 5-run averagePlatform: Apple Mac Mini M4 Pro, Node.js 24.8.0Fastify framework: 14,283 ops/sec (0.070ms avg) 🥇 FASTESTWoodland framework: 12,478 ops/sec (0.080ms avg) 🥈 Strong secondExpress.js framework: 12,112 ops/sec (0.083ms avg) 🥉 Third placeRaw Node.js HTTP module: 10,888 ops/sec (0.092ms avg)Performance improvement: +15% faster than raw Node.js, +3% faster than Express.js, 87% of Fastify's performanceWhy Woodland delivers competitive performance:
- vs Raw Node.js: Optimized request/response pipeline that eliminates common inefficiencies (+15% faster)
- vs Express.js: Lightweight middleware system that outperforms while maintaining developer experience (+3% faster)
- vs Fastify: Balanced approach that trades some raw speed for enhanced usability (87% of Fastify's performance)
- Built-in JSON response optimization with smart serialization
- Efficient header management and intelligent caching strategies
- Developer-friendly architecture that doesn't sacrifice performance for convenience
Node.js 24.8.0 (1000 iterations, 100 warmup, averaged across 5 runs)
HTTP Operations404 handling: 16,570 ops/sec (0.060ms avg)Parameterized routes: 14,971 ops/sec (0.067ms avg)Error handling: 14,859 ops/sec (0.067ms avg)JSON response: 14,422 ops/sec (0.069ms avg)Simple GET: 13,497 ops/sec (0.074ms avg)Middleware chain: 12,108 ops/sec (0.083ms avg)Large response: 814 ops/sec (1.228ms avg)- Choose Woodland over alternatives: Woodland provides 15% better performance than raw Node.js and 3% better than Express.js for JSON responses
- Enable Route Caching: Route caching provides significant performance improvement - allows() with cache: 4.8M ops/sec vs without: 300K ops/sec
- Optimize Route Order: Place frequently accessed routes first in your application
- Use Parameter Routes: Parameter routes perform competitively with static routes (~2.4M vs ~2.5M ops/sec)
- Enable ETags: Reduces bandwidth for unchanged resources (333K ops/sec with ETags)
- Stream Large Files: Use built-in streaming for files (330K ops/sec streaming performance)
- Minimize Middleware: Only use necessary middleware - complex middleware reduces performance
- Leverage Built-in Utilities: Use woodland's optimized utility functions (7.7M+ ops/sec for common operations)
- Configure Appropriate Caching: Set proper cache headers and TTL values
- Use Proper HTTP Methods: DELETE requests show best performance (15.7K ops/sec) for CRUD operations
git clone https://github.com/avoidwork/woodland.gitcd woodlandnpm install# Run all benchmarksnpm run benchmark# Run specific benchmark suitesnode benchmark.js routing utility servingnode benchmark.js http middleware comparison# Run with custom settingsnode benchmark.js --iterations 2000 --warmup 200# Run specific suite with custom settingsnode benchmark.js utility -i 500 -w 50
Available benchmark suites:
comparison- Framework vs raw Node.js HTTP module performancehttp- End-to-end HTTP server performancemiddleware- Middleware registration and executionrouting- Route matching and resolutionserving- File serving and streamingutility- Core utility functions
Woodland maintains100% statement coverage with comprehensive testing across all features. The CLI module achieves100% coverage with rigorous testing of all code paths including successful server startup, and the utility module achieves100% line coverage with comprehensive edge case testing.
npmtest386 passing (6s)--------------|---------|----------|---------|---------|-------------------File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s --------------|---------|----------|---------|---------|-------------------All files | 100 | 100 | 100 | 100 | cli.js | 100 | 100 | 100 | 100 | constants.js | 100 | 100 | 100 | 100 | utility.js | 100 | 100 | 100 | 100 | woodland.js | 100 | 100 | 100 | 100 | --------------|---------|----------|---------|---------|-------------------- CLI Tests (100% coverage) - Argument parsing, port/IP validation, server startup with HTTP verification, error handling, logging configuration, edge cases
- Security Integration Tests - Path traversal protection, IP security, CORS enforcement, autoindex security, security headers
- Constants Tests - HTTP methods, status codes, headers, content types, server info, export validation
- Security Utility Functions - File path validation, sanitization, HTML escaping, IPv4/IPv6 validation
- Utility Functions - Autoindex generation, status resolution, MIME detection, parameter parsing, URL processing, timing utilities
- Woodland Core Tests - Constructor configuration, HTTP method handlers, middleware registration, routing, CORS handling
- Stream Method Tests - File headers, different file types, range requests, ETags, binary files
- Range Request Tests - String content, invalid ranges, streams, partial content delivery
- Cache Functionality - Route caching, allows caching, cache eviction, permissions caching
- Serve Method Tests - Text files, HTML files, binary files, 404 handling, directory redirection, index files, autoindex, nested paths, large files
- Middleware Tests - Execution order, error propagation, parameterized routes, exit functionality, wildcard middleware
- Response Helper Tests - JSON responses, redirects, header manipulation, status codes, error handling
import{woodland}from"woodland";importassertfrom"node:assert";describe("My API",()=>{letapp;beforeEach(()=>{app=woodland();});it("should respond to GET /",async()=>{app.get("/",(req,res)=>res.send("Hello"));constreq={method:"GET",url:"/",headers:{}};constres={statusCode:200,headers:{},setHeader:(k,v)=>res.headers[k]=v,end:(body)=>res.body=body};app.route(req,res);assert.equal(res.body,"Hello");});});
import{Woodland,woodland}from"woodland";import{IncomingMessage,ServerResponse}from"node:http";// Using factory functionconstapp=woodland({defaultHeaders:{"content-type":"application/json"}});// Using classclassMyAPIextendsWoodland{constructor(){super({time:true});}}// Custom middleware with typesinterfaceCustomRequestextendsIncomingMessage{user?:{id:string,name:string};}constauthenticate=(req:CustomRequest,res:ServerResponse,next:()=>void):void=>{req.user={id:"123",name:"John"};next();};
interfaceWoodlandConfig{autoindex?:boolean;cacheSize?:number;cacheTTL?:number;charset?:string;corsExpose?:string;defaultHeaders?:Record<string,string>;digit?:number;etags?:boolean;indexes?:string[];logging?:{enabled?:boolean;format?:string;level?:string;};origins?:string[];silent?:boolean;time?:boolean;}
import{createServer}from"node:http";import{woodland}from"woodland";constapp=woodland({defaultHeaders:{"content-type":"application/json"},time:true});constusers=newMap();// Middlewareapp.always(async(req,res,next)=>{if(req.method==="POST"||req.method==="PUT"){letbody="";req.on("data",chunk=>body+=chunk);req.on("end",()=>{try{req.body=JSON.parse(body);}catch(e){returnres.error(400,"Invalid JSON");}next();});}else{next();}});// Routesapp.get("/users",(req,res)=>{res.json(Array.from(users.values()));});app.get("/users/:id",(req,res)=>{constuser=users.get(req.params.id);if(!user){returnres.error(404,"User not found");}res.json(user);});app.post("/users",(req,res)=>{const{name, email}=req.body;if(!name||!email){returnres.error(400,"Name and email required");}constid=Date.now().toString();constuser={id, name, email};users.set(id,user);res.json(user,201);});app.put("/users/:id",(req,res)=>{constuser=users.get(req.params.id);if(!user){returnres.error(404,"User not found");}Object.assign(user,req.body);res.json(user);});app.delete("/users/:id",(req,res)=>{if(!users.has(req.params.id)){returnres.error(404,"User not found");}users.delete(req.params.id);res.status(204).send("");});createServer(app.route).listen(3000);
import{createServer}from"node:http";import{woodland}from"woodland";import{createWriteStream}from"node:fs";import{pipeline}from"node:stream/promises";constapp=woodland();app.post("/upload",async(req,res)=>{try{constfilename=req.headers["x-filename"]||"upload.bin";constwriteStream=createWriteStream(`./uploads/${filename}`);awaitpipeline(req,writeStream);res.json({message:"Upload successful", filename});}catch(error){res.error(500,"Upload failed");}});createServer(app.route).listen(3000);
import{createServer}from"node:http";import{WebSocketServer}from"ws";import{woodland}from"woodland";constapp=woodland();constserver=createServer(app.route);constwss=newWebSocketServer({server});app.get("/",(req,res)=>{res.send(` <!DOCTYPE html> <html> <head><title>WebSocket Test</title></head> <body> <script> const ws = new WebSocket('ws://localhost:3000'); ws.onmessage = e => console.log('Received:', e.data); ws.onopen = () => ws.send('Hello Server!'); </script> </body> </html> `);});wss.on("connection",(ws)=>{ws.send("Welcome to WebSocket server!");ws.on("message",(data)=>{console.log("Received:",data.toString());});});server.listen(3000);
// Problem: CORS blocked requests// Solution: Configure origins properlyconstapp=woodland({origins:["https://myapp.com","http://localhost:3000"]});
// Problem: Routes not matching// Solution: Check route patternsapp.get("/users/:id",handler);// ✅ Correctapp.get("/users/:id/",handler);// ❌ Trailing slashapp.get("/users/([0-9]+)",handler);// ✅ RegExp pattern
// The 'routes' method builds middleware execution order as follows:// 1. Always middleware (WILDCARD) - added first to the middleware array// 2. Route-specific middleware - added after all always middleware// ✅ Understanding the routes method behavior:app.always(corsHandler);// Added to WILDCARD middleware mapapp.always(requestLogger);// Added to WILDCARD middleware mapapp.post("/users",authenticate,createUser);// Added to POST middleware map// When routes("/users", "POST") is called, the middleware array becomes:// [corsHandler, requestLogger, authenticate, createUser]// with exit point set between requestLogger and authenticate// ✅ Always middleware executes first regardless of registration order:app.post("/api/users",validate,createUser);// Route registered firstapp.always(securityHeaders);// Always middleware registered afterapp.always(bodyParser);// Another always middleware// Execution order for POST /api/users:// 1. securityHeaders (always middleware)// 2. bodyParser (always middleware)// 3. validate (route middleware)// 4. createUser (route handler)// ❌ Common misconception - registration order between always/route doesn't matter:// The routes method ALWAYS puts always middleware first in the execution chain
// Problem: High memory usage// Solution: Tune cache settingsconstapp=woodland({cacheSize:100,// Reduce cache sizecacheTTL:60000// Shorter TTL});
constapp=woodland({logging:{enabled:true,level:"debug"}});// Enable debug logsapp.log("Debug message","debug");
- Check middleware overhead: Profile middleware execution
- Optimize route patterns: Use specific patterns vs wildcards
- Enable caching: Use ETags and cache headers
- Monitor memory: Watch for memory leaks in long-running apps
Copyright (c) 2025 Jason Mulligan
Licensed under theBSD-3-Clause license.
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass
- Submit a pull request
- Issues:GitHub Issues
- Documentation:GitHub Wiki
- Discussions:GitHub Discussions
About
High-performance HTTP framework
Resources
License
Uh oh!
There was an error while loading.Please reload this page.
Stars
Watchers
Forks
Sponsor this project
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.
Contributors2
Uh oh!
There was an error while loading.Please reload this page.