View on GitHub

Woodland

High-performance HTTP framework

Download this project as a .zip file Download this project as a tar.gz file
Woodland Logo

Woodland

High-performance HTTP framework

πŸš€ Features

πŸ”’ Security & OWASP Compliance

Woodland follows a security-first design philosophy with strong adherence to OWASP guidelines:

OWASP Top 10 Coverage: Excellent protection against injection attacks, broken access control, security misconfigurations, and cross-site scripting. See Technical Documentation for complete assessment.

πŸ’‘ Quick Security Setup: Add essential security middleware for production deployment:

import helmet from 'helmet';
import rateLimit from 'express-rate-limit';

// Security headers
app.use(helmet());

// Rate limiting
app.use(rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100 // limit each IP to 100 requests per windowMs
}));

πŸ’‘ Why Choose Woodland?

Stop accepting framework overhead. Most HTTP frameworks slow you down in exchange for convenience. Woodland breaks that trade-off.

πŸ† Proven Performance: Comprehensive benchmarks show Woodland outperforms raw Node.js by 29%, Express.js by 56%, and is competitive with Fastify
⚑ 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.

πŸ“¦ Installation

# npm
npm install woodland

# yarn
yarn add woodland

# pnpm
pnpm add woodland

# Global installation for CLI
npm install -g woodland

πŸš€ Quick Start

Basic Server

import {createServer} from "node:http";
import {woodland} from "woodland";

const app = 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");
});

Using the Class

import {Woodland} from "woodland";

class MyAPI extends Woodland {
  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: new Date().toISOString()});
  }
  
  createUser(req, res) {
    // Handle user creation
    res.status(201).json({message: "User created"});
  }
}

const api = new MyAPI();

πŸ“– Table of Contents

βš™οΈ Configuration

Default Configuration

const app = woodland({
  autoindex: false,        // Enable directory browsing
  cacheSize: 1000,        // Internal cache size
  cacheTTL: 10000,        // Cache TTL (10 seconds)
  charset: "utf-8",       // Default charset
  corsExpose: "",         // CORS exposed headers
  defaultHeaders: {},     // Default response headers
  digit: 3,              // Timing precision digits
  etags: true,           // Enable ETag generation
  indexes: [             // Index file names
    "index.htm",
    "index.html"
  ],
  logging: {
    enabled: true,       // Enable logging
    format: "%h %l %u %t \"%r\" %>s %b", // Log format
    level: "info"        // Log level
  },
  origins: [],           // CORS origins (empty array denies all cross-origin requests)
  silent: false,         // Disable default headers
  time: false           // Enable response timing
});

Advanced Configuration

const app = woodland({
  // Security headers
  defaultHeaders: {
    "x-content-type-options": "nosniff",
    "x-frame-options": "DENY",
    "x-xss-protection": "1; mode=block",
    "strict-transport-security": "max-age=31536000; includeSubDomains"
  },
  
  // CORS configuration
  origins: [
    "https://myapp.com",
    "https://api.myapp.com"
  ],
  corsExpose: "x-custom-header,x-request-id",
  
  // Performance tuning
  cacheSize: 5000,
  cacheTTL: 600000, // 10 minutes
  
  // Detailed logging
  logging: {
    enabled: true,
    level: "debug",
    format: "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\""
  },
  
  // Enable features
  time: true,
  etags: true,
  autoindex: true
});

πŸ›€οΈ Routing

Basic Routes

// HTTP methods
app.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 parameters
app.get("/users/:id", (req, res) => {
  const userId = req.params.id;
  res.json({id: userId});
});

// Multiple parameters
app.get("/users/:userId/posts/:postId", (req, res) => {
  const {userId, postId} = req.params;
  res.json({userId, postId});
});

Advanced Routing

// RegExp patterns
app.get("/api/v[1-3]/users", (req, res) => {
  res.json({version: req.url.match(/v(\d)/)[1]});
});

// Wildcard routes
app.get("/files/(.*)", (req, res) => {
  // Serve any file under /files/
});

// Route with validation
app.get("/users/:id(\\d+)", (req, res) => {
  // Only matches numeric IDs
  res.json({id: parseInt(req.params.id)});
});

Route Groups

// API v1 routes
const apiV1 = (req, res, next) => {
  req.version = "v1";
  next();
};

app.get("/api/v1/users", apiV1, getAllUsers);
app.post("/api/v1/users", apiV1, createUser);

// Protected routes
const authenticate = (req, res, next) => {
  const token = req.headers.authorization;
  if (!token) {
    return res.error(401);
  }
  // Verify token...
  next();
};

app.get("/admin/*", authenticate, adminHandler);

πŸ”§ Middleware

Basic Middleware

// Global middleware
app.always((req, res, next) => {
  console.log(`${req.method} ${req.url}`);
  next();
});

// Route-specific middleware
app.get("/protected", authenticate, authorize, handler);

// ❌ WRONG: Do NOT register error middleware with 'always'
// This will execute BEFORE route handlers, not after errors occur
app.always((error, req, res, next) => {
  if (error) {
    console.error(error);
    res.error(500);
  } else {
    next();
  }
});

// βœ… CORRECT: Register error middleware with specific routes LAST
app.get("/api/users", 
  authenticate,        // Normal middleware
  authorize,          // Normal middleware
  getUserHandler,     // Route handler
  (error, req, res, next) => {  // Error middleware - LAST
    if (error) {
      console.error(error);
      res.error(500);
    } else {
      next();
    }
  }
);

// βœ… CORRECT: Global error handling should be done with route patterns
app.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:

Middleware Examples

// Request logging
const requestLogger = (req, res, next) => {
  const start = Date.now();
  res.on("finish", () => {
    const duration = Date.now() - start;
    console.log(`${req.method} ${req.url} - ${res.statusCode} (${duration}ms)`);
  });
  next();
};

// Body parser
const bodyParser = async (req, res, next) => {
  if (req.method === "POST" || req.method === "PUT") {
    let body = "";
    req.on("data", chunk => body += chunk);
    req.on("end", () => {
      try {
        req.body = JSON.parse(body);
      } catch (e) {
        req.body = body;
      }
      next();
    });
  } else {
    next();
  }
};

// Rate limiting
const rateLimit = (() => {
  const requests = new Map();
  return (req, res, next) => {
    const ip = req.ip;
    const now = Date.now();
    const window = 60000; // 1 minute
    const limit = 100;
    
    if (!requests.has(ip)) {
      requests.set(ip, []);
    }
    
    const ipRequests = requests.get(ip);
    const recentRequests = ipRequests.filter(time => now - time < window);
    
    if (recentRequests.length >= limit) {
      return res.error(429);
    }
    
    recentRequests.push(now);
    requests.set(ip, recentRequests);
    next();
  };
})();

πŸ“ Static Files

Basic File Serving

// Serve files from public directory
app.files("/static", "./public");

// Serve with custom options
app.get("/downloads/(.*)", (req, res) => {
  const filename = req.params[0];
  const filepath = path.join("./downloads", filename);
  
  // Custom file serving logic
  app.serve(req, res, filename, "./downloads");
});

Directory Browsing

const app = woodland({
  autoindex: true,  // Enable directory browsing
  indexes: ["index.html", "index.htm", "default.html"]
});

app.files("/", "./public");

🌐 CORS

Woodland handles CORS automatically when you configure origins. Here’s what you get for free:

const app = 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

What Woodland Does Automatically

  1. Preflight Route Registration: When origins are configured, Woodland automatically registers an OPTIONS handler that responds with 204 No Content
  2. CORS Headers: For valid cross-origin requests, automatically sets all required CORS headers
  3. Origin Validation: Checks request origin against configured allowed origins
  4. Method Detection: Access-Control-Allow-Methods reflects actual registered routes
  5. Security: Empty origins array denies all CORS requests by default

Manual CORS Control (When Needed)

// Override automatic behavior for specific routes
app.options("/api/special", (req, res) => {
  res.header("access-control-allow-methods", "GET,POST"); // Restrict methods
  res.header("access-control-allow-headers", "content-type"); // Restrict headers
  res.header("access-control-max-age", "86400"); // Set cache duration
  res.send("");
});

// Dynamic origin validation (replaces automatic validation)
app.always((req, res, next) => {
  const origin = req.headers.origin;
  
  // Custom logic for origin validation
  if (isValidOriginForUser(origin, req.user)) {
    res.header("access-control-allow-origin", origin);
  }
  
  next();
});

// Conditional CORS (disable automatic CORS, use manual)
const app = woodland({
  origins: [] // Empty = no automatic CORS
});

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();
});

Most applications only need to configure origins and corsExpose - manual CORS handling is rarely necessary.

❌ Error Handling

Built-in Error Handling

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"
  });
});

Custom Error Handler

app.on("error", (req, res, err) => {
  console.error(`Error ${res.statusCode}: ${err}`);
  
  // Log to external service
  if (res.statusCode >= 500) {
    logError(err, req);
  }
});

// Global error catching
app.always((req, res, next) => {
  try {
    next();
  } catch (error) {
    res.error(500, error.message);
  }
});

πŸ“€ Response Helpers

JSON Responses

app.get("/users/:id", (req, res) => {
  const user = {id: req.params.id, name: "John Doe"};
  res.json(user);
});

app.post("/users", (req, res) => {
  const user = createUser(req.body);
  res.json(user, 201);
});

Redirects

app.get("/old-path", (req, res) => {
  res.redirect("/new-path");
});

app.get("/temporary", (req, res) => {
  res.redirect("/permanent", false); // Temporary redirect
});

Custom Headers

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"}));
});

🎯 Event Handlers

Available Events

// Connection established
app.on("connect", (req, res) => {
  console.log(`Connection from ${req.ip}`);
});

// Request finished
app.on("finish", (req, res) => {
  console.log(`Request completed: ${req.method} ${req.url}`);
});

// Error occurred
app.on("error", (req, res, err) => {
  console.error(`Error: ${err}`);
});

// File streaming
app.on("stream", (req, res) => {
  console.log(`Streaming file to ${req.ip}`);
});

Event-Driven Analytics

app.on("finish", (req, res) => {
  const metrics = {
    method: req.method,
    url: req.url,
    status: res.statusCode,
    ip: req.ip,
    userAgent: req.headers["user-agent"],
    timestamp: new Date().toISOString()
  };
  
  // Send to analytics service
  analytics.track(metrics);
});

πŸ“Š Logging

Log Levels

Custom Logging

const app = woodland({
  logging: {
    enabled: true,
    level: "debug",
    format: "%h %l %u %t \"%r\" %>s %b %D"
  }
});

// Manual logging
app.log("Custom message", "info");
app.log("Debug information", "debug");

Log Format Placeholders

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

πŸ’» CLI Usage

Basic Usage

# Serve current directory
woodland

# Custom IP and port
woodland --ip=0.0.0.0 --port=3000

# Disable logging
woodland --logging=false

# Serve specific directory
cd /path/to/files && woodland

The CLI achieves 100% test coverage with comprehensive unit tests covering argument parsing, validation, server configuration, error handling scenarios, and actual HTTP request serving verification.

CLI Options

Option Default Description
--ip 127.0.0.1 Server IP address
--port 8000 Server port
--logging true Enable/disable request logging

Example Output

$ woodland --port=3000
id=woodland, hostname=localhost, ip=127.0.0.1, port=3000
127.0.0.1 - [18/Dec/2024:10:30:00 -0500] "GET / HTTP/1.1" 200 1327
127.0.0.1 - [18/Dec/2024:10:30:05 -0500] "GET /favicon.ico HTTP/1.1" 404 9

πŸ“š API Reference

Woodland Class

Constructor

new Woodland(config)

HTTP Methods

Utility Methods

Lifecycle Hooks

Request Object Extensions

Response Object Extensions

⚑ Performance

πŸ† Framework Performance Showdown

Proven Performance Leadership: Woodland delivers exceptional performance that outpaces raw Node.js and Express.js, while remaining competitive with Fastify.

Framework Comparison (JSON Response) - Averaged across 5 runs
Fastify framework:        14,452 ops/sec  (0.069ms avg)  πŸ₯‡ FASTEST
Woodland framework:       14,173 ops/sec  (0.071ms avg)  πŸ₯ˆ Very close second
Raw Node.js HTTP module:  11,061 ops/sec  (0.090ms avg)  πŸ₯‰ Third place
Express.js framework:      9,374 ops/sec  (0.107ms avg)

Performance improvement: +28% faster than raw Node.js, +51% faster than Express.js, 98% of Fastify's performance

Why Woodland delivers exceptional performance:

Benchmark Results

Node.js 23.10.0 on Apple M4 Pro Mac Mini (1000 iterations, 100 warmup, averaged across 5 runs)

Routing Operations
Allowed methods:    4,797,153 ops/sec  (0.0002ms avg)
Path conversion:    2,561,369 ops/sec  (0.0004ms avg)
Parameter routes:   2,416,581 ops/sec  (0.0004ms avg)
Static routes:      2,467,653 ops/sec  (0.0004ms avg)
Not found routes:   2,479,326 ops/sec  (0.0004ms avg)
Route caching:      1,388,793 ops/sec  (0.0007ms avg)
Allowed cache:      2,092,956 ops/sec  (0.0005ms avg)
Route resolution:   917,799 ops/sec    (0.0011ms avg)

Utility Operations
Number padding:     7,695,461 ops/sec  (0.0001ms avg)
MIME detection:     4,619,160 ops/sec  (0.0002ms avg)
Middleware chain:   3,759,994 ops/sec  (0.0003ms avg)
Time formatting:    3,512,863 ops/sec  (0.0003ms avg)
Content pipeability: 3,214,295 ops/sec  (0.0003ms avg)
URL parsing:        2,838,552 ops/sec  (0.0004ms avg)
Status determination: 3,095,503 ops/sec  (0.0003ms avg)
Timezone offset:    4,638,018 ops/sec  (0.0002ms avg)
Parameter extraction: 1,059,064 ops/sec   (0.0009ms avg)
Directory listing:   470,633 ops/sec    (0.0021ms avg)

File Serving Operations
Static file serving: 582,978 ops/sec   (0.0017ms avg)
Stream operations:   330,495 ops/sec   (0.0030ms avg)
ETag generation:     336,330 ops/sec   (0.0030ms avg)
Stream with ETags:   332,530 ops/sec   (0.0030ms avg)
HEAD requests:       68,935 ops/sec    (0.015ms avg)
Small files:         39,396 ops/sec    (0.025ms avg)
Large files:         42,231 ops/sec    (0.024ms avg)
Medium files:        41,680 ops/sec    (0.024ms avg)
Directory listing:   19,076 ops/sec    (0.052ms avg)
Directory autoindex: 18,224 ops/sec    (0.055ms avg)

HTTP Operations
Server startup:      113,810 ops/sec   (0.009ms avg)
DELETE requests:     15,687 ops/sec    (0.064ms avg)
Complex middleware:  14,593 ops/sec    (0.069ms avg)
Nested routes:       14,368 ops/sec    (0.070ms avg)
404 handling:        12,816 ops/sec    (0.078ms avg)
Parameterized routes: 13,629 ops/sec   (0.073ms avg)
JSON response:       12,568 ops/sec    (0.080ms avg)
Error handling:      12,631 ops/sec    (0.079ms avg)
PUT requests:        11,169 ops/sec    (0.090ms avg)
Middleware chain:    10,993 ops/sec    (0.091ms avg)
Mixed workload:      10,916 ops/sec    (0.092ms avg)
POST requests:       10,595 ops/sec    (0.094ms avg)
Simple GET:          12,515 ops/sec    (0.080ms avg)
Large response:      922 ops/sec       (1.084ms avg)

Performance Tips

  1. Choose Woodland over alternatives: Woodland provides 28% better performance than raw Node.js and 51% better than Express.js for JSON responses
  2. Enable Route Caching: Route caching provides significant performance improvement - allows() with cache: 4.8M ops/sec vs without: 300K ops/sec
  3. Optimize Route Order: Place frequently accessed routes first in your application
  4. Use Parameter Routes: Parameter routes perform competitively with static routes (~2.4M vs ~2.5M ops/sec)
  5. Enable ETags: Reduces bandwidth for unchanged resources (333K ops/sec with ETags)
  6. Stream Large Files: Use built-in streaming for files (330K ops/sec streaming performance)
  7. Minimize Middleware: Only use necessary middleware - complex middleware reduces performance
  8. Leverage Built-in Utilities: Use woodland’s optimized utility functions (7.7M+ ops/sec for common operations)
  9. Configure Appropriate Caching: Set proper cache headers and TTL values
  10. Use Proper HTTP Methods: DELETE requests show best performance (15.7K ops/sec) for CRUD operations

Running Benchmarks

git clone https://github.com/avoidwork/woodland.git
cd woodland
npm install

# Run all benchmarks
npm run benchmark

# Run specific benchmark suites
node benchmark.js routing utility serving
node benchmark.js http middleware comparison

# Run with custom settings
node benchmark.js --iterations 2000 --warmup 200

# Run specific suite with custom settings
node benchmark.js utility -i 500 -w 50

Available benchmark suites:

πŸ§ͺ Testing

Test Coverage

Woodland maintains 100% statement coverage with comprehensive testing across all features. The CLI module achieves 100% coverage with rigorous testing of all code paths including successful server startup, and the utility module achieves 100% line coverage with comprehensive edge case testing.

npm test

Test Results

386 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 |                   
--------------|---------|----------|---------|---------|-------------------

Test Categories

Writing Tests

import {woodland} from "woodland";
import assert from "node:assert";

describe("My API", () => {
  let app;
  
  beforeEach(() => {
    app = woodland();
  });
  
  it("should respond to GET /", async () => {
    app.get("/", (req, res) => res.send("Hello"));
    
    const req = {method: "GET", url: "/", headers: {}};
    const res = {
      statusCode: 200,
      headers: {},
      setHeader: (k, v) => res.headers[k] = v,
      end: (body) => res.body = body
    };
    
    app.route(req, res);
    assert.equal(res.body, "Hello");
  });
});

πŸ“˜ TypeScript

Type Definitions

import {Woodland, woodland} from "woodland";
import {IncomingMessage, ServerResponse} from "node:http";

// Using factory function
const app = woodland({
  defaultHeaders: {"content-type": "application/json"}
});

// Using class
class MyAPI extends Woodland {
  constructor() {
    super({time: true});
  }
}

// Custom middleware with types
interface CustomRequest extends IncomingMessage {
  user?: {id: string, name: string};
}

const authenticate = (
  req: CustomRequest,
  res: ServerResponse,
  next: () => void
): void => {
  req.user = {id: "123", name: "John"};
  next();
};

Configuration Types

interface WoodlandConfig {
  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;
}

πŸ” Examples

REST API

import {createServer} from "node:http";
import {woodland} from "woodland";

const app = woodland({
  defaultHeaders: {"content-type": "application/json"},
  time: true
});

const users = new Map();

// Middleware
app.always(async (req, res, next) => {
  if (req.method === "POST" || req.method === "PUT") {
    let body = "";
    req.on("data", chunk => body += chunk);
    req.on("end", () => {
      try {
        req.body = JSON.parse(body);
      } catch (e) {
        return res.error(400, "Invalid JSON");
      }
      next();
    });
  } else {
    next();
  }
});

// Routes
app.get("/users", (req, res) => {
  res.json(Array.from(users.values()));
});

app.get("/users/:id", (req, res) => {
  const user = users.get(req.params.id);
  if (!user) {
    return res.error(404, "User not found");
  }
  res.json(user);
});

app.post("/users", (req, res) => {
  const {name, email} = req.body;
  if (!name || !email) {
    return res.error(400, "Name and email required");
  }
  
  const id = Date.now().toString();
  const user = {id, name, email};
  users.set(id, user);
  res.json(user, 201);
});

app.put("/users/:id", (req, res) => {
  const user = users.get(req.params.id);
  if (!user) {
    return res.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)) {
    return res.error(404, "User not found");
  }
  
  users.delete(req.params.id);
  res.status(204).send("");
});

createServer(app.route).listen(3000);

File Upload API

import {createServer} from "node:http";
import {woodland} from "woodland";
import {createWriteStream} from "node:fs";
import {pipeline} from "node:stream/promises";

const app = woodland();

app.post("/upload", async (req, res) => {
  try {
    const filename = req.headers["x-filename"] || "upload.bin";
    const writeStream = createWriteStream(`./uploads/${filename}`);
    
    await pipeline(req, writeStream);
    res.json({message: "Upload successful", filename});
  } catch (error) {
    res.error(500, "Upload failed");
  }
});

createServer(app.route).listen(3000);

WebSocket Integration

import {createServer} from "node:http";
import {WebSocketServer} from "ws";
import {woodland} from "woodland";

const app = woodland();
const server = createServer(app.route);
const wss = new WebSocketServer({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);

πŸ”§ Troubleshooting

Common Issues

CORS Errors

// Problem: CORS blocked requests
// Solution: Configure origins properly
const app = woodland({
  origins: ["https://myapp.com", "http://localhost:3000"]
});

Route Not Found

// Problem: Routes not matching
// Solution: Check route patterns
app.get("/users/:id", handler);        // βœ… Correct
app.get("/users/:id/", handler);       // ❌ Trailing slash
app.get("/users/([0-9]+)", handler);   // βœ… RegExp pattern

Middleware Order

// 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 map
app.always(requestLogger);      // Added to WILDCARD middleware map  
app.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 first
app.always(securityHeaders);   // Always middleware registered after
app.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

Memory Issues

// Problem: High memory usage
// Solution: Tune cache settings
const app = woodland({
  cacheSize: 100,    // Reduce cache size
  cacheTTL: 60000   // Shorter TTL
});

Debug Mode

const app = woodland({
  logging: {
    enabled: true,
    level: "debug"
  }
});

// Enable debug logs
app.log("Debug message", "debug");

Performance Issues

  1. Check middleware overhead: Profile middleware execution
  2. Optimize route patterns: Use specific patterns vs wildcards
  3. Enable caching: Use ETags and cache headers
  4. Monitor memory: Watch for memory leaks in long-running apps

πŸ“„ License

Copyright (c) 2025 Jason Mulligan

Licensed under the BSD-3-Clause license.

🀝 Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Add tests for new functionality
  4. Ensure all tests pass
  5. Submit a pull request

πŸ“ž Support


Built with ❀️ by Jason Mulligan