diff --git a/docs/README.md b/docs/README.md
index dc66c90..014bc94 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -5,17 +5,38 @@
[](https://www.npmjs.com/package/0http)
[](https://github.com/jkyberneees/0http)
-
+
+
+

+
+
+
Zero friction HTTP framework for Node.js
+
+
Tweaked for high throughput, low overhead, and maximum flexibility.
+
+
+
+## Why 0http?
+
+- đ **Blazing Fast**: One of the fastest Node.js web frameworks. Optimized for speed with smart caching and efficient routing.
+- đ **Highly Configurable**: Swap routers, servers, and customize behavior to fit your needs.
+- đ **Middleware Support**: Express-like middleware chain with full `async/await` support.
+- ĘŚ **TypeScript Ready**: First-class TypeScript support for type-safe development.
+- đ§Š **Nested Routing**: Powerful nested router support for modular architectures, optimized for static paths.
+- đĄď¸ **Production Ready**: Secure defaults with environment-aware error handling.
+
+---
+
+## Installation
-Zero friction HTTP framework:
-- Tweaked Node.js HTTP server for high throughput.
-- High-performance and customizable request routers.
+```bash
+npm install 0http
+```
-
-> Check it yourself: https://web-frameworks-benchmark.netlify.app/result?f=feathersjs,0http,koa,fastify,nestjs-express,express,sails,nestjs-fastify,restana
+## Quick Start
+
+### JavaScript
-# Usage
-JavaScript:
```js
const zero = require('0http')
const { router, server } = zero()
@@ -25,17 +46,17 @@ router.get('/hello', (req, res) => {
})
router.post('/do', (req, res) => {
- // ...
res.statusCode = 201
- res.end()
+ res.end('Done!')
})
-//...
-
-server.listen(3000)
+server.listen(3000, () => {
+ console.log('Server listening on port 3000')
+})
```
-TypeScript:
+### TypeScript
+
```ts
import zero from '0http'
import { Protocol } from '0http/common'
@@ -43,85 +64,78 @@ import { Protocol } from '0http/common'
const { router, server } = zero()
router.use((req, res, next) => {
+ console.log('Request received')
return next()
})
router.get('/hi', (req, res) => {
- res.end(`Hello World from TS!`)
+ res.end('Hello World from TS!')
})
server.listen(3000)
```
-# Routers
-`0http` allows you to define the router implementation you prefer as soon as it support the following interface:
+---
+
+## Core Capabilities
+
+### 1. Pluggable Routers
+`0http` allows you to define the router implementation you prefer.
+
+#### Sequential Router (Default)
+An extended implementation of [trouter](https://www.npmjs.com/package/trouter).
+- **Features**: Middleware support, nested routers, regex matching.
+- **Performance**: Uses an internal LRU cache (optional) to store matching results, making it extremely fast even with many routes.
+- **Supported Verbs**: `GET, HEAD, PATCH, OPTIONS, CONNECT, DELETE, TRACE, POST, PUT`
+
```js
-router.lookup = (req, res) // -> should trigger router search and handlers execution
+const sequential = require('0http/lib/router/sequential')
+const { router } = zero({
+ router: sequential({
+ cacheSize: 2000 // Configurable cache size
+ })
+})
```
-## 0http - sequential (default router)
-This a `0http` extended implementation of the [trouter](https://www.npmjs.com/package/trouter) router. Includes support for middlewares, nested routers and shortcuts for routes registration.
-As this is an iterative regular expression matching router, it tends to be slower than `find-my-way` when the number of registered routes increases; to mitigate this issue, we use
-an internal(optional) LRU cache to store the matching results of the previous requests, resulting on a super-fast matching process.
-
-Supported HTTP verbs: `GET, HEAD, PATCH, OPTIONS, CONNECT, DELETE, TRACE, POST, PUT`
+#### Find-My-Way Router
+Integration with [find-my-way](https://github.com/delvedor/find-my-way), a super-fast Radix Tree router.
+- **Best for**: Static paths and high performance without regex overhead.
+- **Note**: Does not support all the middleware goodies of the sequential router.
```js
-const zero = require('0http')
-const { router, server } = zero({})
+const { router } = zero({
+ router: require('find-my-way')()
+})
+```
+
+### 2. Middleware Engine
+The middleware engine is optimized for performance and flexibility.
-// global middleware example
+#### Global & Route Middleware
+```js
+// Global middleware
router.use('/', (req, res, next) => {
- res.write('Hello ')
+ res.setHeader('X-Powered-By', '0http')
next()
})
-// route middleware example
-const routeMiddleware = (req, res, next) => {
- res.write('World')
+// Route-specific middleware
+const auth = (req, res, next) => {
+ if (!req.headers.authorization) {
+ res.statusCode = 401
+ return res.end('Unauthorized')
+ }
next()
}
-// GET /sayhi route with middleware and handler
-router.get('/sayhi', routeMiddleware, (req, res) => {
- res.end('!')
+router.get('/protected', auth, (req, res) => {
+ res.end('Secret Data')
})
-
-server.listen(3000)
```
-### Configuration Options
-- **defaultRoute**: Route handler when there is no router matching. Default value:
- ```js
- (req, res) => {
- res.statusCode = 404
- res.end()
- }
- ```
-- **cacheSize**: The size of the LRU cache for router matching. If the value is `0`, the cache will be disabled. If the value is `<0`, the cache will have an unlimited size. If the value is `>0`, an LRU Cache will be used. Default value: `-1`, for extreme performance.
-- **errorHandler**: Global error handler function. Default value:
-
- ```js
- (err, req, res) => {
- res.statusCode = 500
- res.end(err.message)
- }
- ```
-
-* **prioRequestsProcessing**: `true` to use SetImmediate to prioritize router lookup, `false` to disable. By default `true`, if used with native Node.js `http` and `https` servers. Set to `false`, if using Node.js Native Addon server, such as uWebSockets.js, as this will cause a huge performance penalty
-Example passing configuration options:
+#### Async/Await Support
+Fully supports async middlewares for clean code.
-```js
-const sequential = require('0http/lib/router/sequential')
-const { router, server } = zero({
- router: sequential({
- cacheSize: 2000
- })
-})
-```
-
-### Async middlewares
-You can use async middlewares to await the remaining chain execution. Let's describe with a custom error handler middleware:
```js
router.use('/', async (req, res, next) => {
try {
@@ -131,91 +145,90 @@ router.use('/', async (req, res, next) => {
res.end(err.message)
}
})
-
-router.get('/sayhi', (req, res) => {
- throw new Error('Uuuups!')
-})
```
-### Nested Routers
-You can simply use `sequential` router instances as nested routers:
-```js
-const zero = require('../index')
-const { router, server } = zero({})
-
-const nested = require('0http/lib/router/sequential')()
-nested.get('/url', (req, res, next) => {
- res.end(req.url)
-})
-router.use('/v1', nested)
+### 3. Nested Routers
+Organize your application with modular nested routers. `0http` optimizes static nested routes for better performance.
-server.listen(3000)
-```
+```js
+const zero = require('0http')
+const { router, server } = zero()
-## find-my-way router
-> https://github.com/delvedor/find-my-way
+const v1 = require('0http/lib/router/sequential')()
-Super-fast raw HTTP router with no goodies. Internally uses a [Radix Tree](https://en.wikipedia.org/wiki/Radix_tree)
-router that will bring better performance over iterative regular expressions matching.
-```js
-const zero = require('../index')
-const { router, server } = zero({
- router: require('find-my-way')()
-})
+v1.get('/users', (req, res) => res.end('User List'))
+v1.get('/posts', (req, res) => res.end('Post List'))
-router.on('GET', '/hi', (req, res) => {
- res.end('Hello World!')
-})
+// Mount the nested router
+router.use('/api/v1', v1)
server.listen(3000)
```
-# Servers
-`0http` is just a wrapper for the servers and routers implementations you provide.
+### 4. Custom Servers
+`0http` is server-agnostic. You can use the standard Node.js `http.Server`, `https.Server`, or even custom implementations.
+
```js
+const https = require('https')
+const fs = require('fs')
const zero = require('0http')
+const options = {
+ key: fs.readFileSync('key.pem'),
+ cert: fs.readFileSync('cert.pem')
+}
+
const { router, server } = zero({
- server: yourCustomServerInstance
+ server: https.createServer(options)
})
+
+server.listen(443)
```
-## Node.js http.Server
-If no server is provided by configuration, the standard Node.js [http.Server](https://nodejs.org/api/http.html#http_class_http_server) implementation is used.
-Because this server offers the best balance between Node.js ecosystem compatibility and performance, we highly recommend it for most use cases.
+---
-# Benchmarks (30/12/2019)
-**Node version**: v12.14.0
-**Laptop**: MacBook Pro 2019, 2,4 GHz Intel Core i9, 32 GB 2400 MHz DDR4
-**Server**: Single instance
+## Configuration Options
-```bash
-wrk -t8 -c40 -d5s http://127.0.0.1:3000/hi
-```
+Pass a configuration object to `zero(config)`:
+
+| Option | Description | Default |
+|--------|-------------|---------|
+| `router` | Custom router instance. | `sequential()` |
+| `server` | Custom server instance. | `http.createServer()` |
+| `defaultRoute` | Handler for 404 Not Found. | `(req, res) => { res.statusCode = 404; res.end() }` |
+| `errorHandler` | Global error handler. | Production-safe error handler (hides stack traces in prod). |
+| `prioRequestsProcessing` | Use `setImmediate` to prioritize request processing. | `true` (for Node.js http/https) |
+
+### Sequential Router Options
+| Option | Description | Default |
+|--------|-------------|---------|
+| `cacheSize` | LRU cache size. `0` to disable, `<0` for unlimited. | `-1` (Unlimited) |
+
+---
+
+## Benchmarks
+
+
+
+> **Note**: Benchmarks are subject to hardware and environment.
+> Check the latest independent results: [Web Frameworks Benchmark](https://web-frameworks-benchmark.netlify.app/result?f=feathersjs,0http,koa,fastify,nestjs-express,express,sails,nestjs-fastify,restana)
+
+**Snapshot (MacBook Pro i9, Node v12):**
+- **0http (sequential)**: ~88k req/sec
+- **0http (find-my-way)**: ~87k req/sec
+- **restana**: ~73k req/sec
+
+---
+
+## Ecosystem
+
+- **[low-http-server](https://github.com/jkyberneees/low-http-server)**: A low-level HTTP server implementation for extreme performance, originally part of 0http.
+
+## Support
+
+If you love this project, consider supporting its maintenance:
+- **PayPal**: [Donate](https://www.paypal.me/kyberneees)
+
+## License
-## 1 route registered
-- 0http (sequential)
- `Requests/sec: 88438.69`
-- 0http (find-my-way)
- `Requests/sec: 87597.44`
-- restana v3.4.2
- `Requests/sec: 73455.97`
-
-## 5 routes registered
-- **0http (sequential)**
- `Requests/sec: 85839.17`
-- 0http (find-my-way)
- `Requests/sec: 82682.86`
-
-> For more accurate benchmarks please see:
->
-> - https://github.com/the-benchmarker/web-frameworks
-
-# Support / Donate đ
-You can support the maintenance of this project:
-- PayPal: https://www.paypal.me/kyberneees
-- [TRON](https://www.binance.com/en/buy-TRON) Wallet: `TJ5Bbf9v4kpptnRsePXYDvnYcYrS5Tyxus`
-
-# Breaking Changes:
-## 3.x
-- Low HTTP server implementation was moved to: https://github.com/jkyberneees/low-http-server
\ No newline at end of file
+MIT
\ No newline at end of file
diff --git a/lib/next.js b/lib/next.js
index 7a9e410..ed376f0 100644
--- a/lib/next.js
+++ b/lib/next.js
@@ -39,7 +39,11 @@ function next (middlewares, req, res, index, routers, defaultRoute, errorHandler
req.preRouterPath = req.path
// Replace pattern in URL - this is a hot path, optimize it
- req.url = req.url.replace(pattern, '')
+ if (typeof pattern === 'number') {
+ req.url = req.url.slice(pattern)
+ } else {
+ req.url = req.url.replace(pattern, '')
+ }
// Ensure URL starts with a slash - use charCodeAt for performance
if (req.url.length === 0 || req.url.charCodeAt(0) !== 47) { // 47 is '/'
diff --git a/lib/router/sequential.js b/lib/router/sequential.js
index fc38206..3a62b5a 100644
--- a/lib/router/sequential.js
+++ b/lib/router/sequential.js
@@ -15,8 +15,11 @@ const DEFAULT_ROUTE = (req, res) => {
const DEFAULT_ERROR_HANDLER = (err, req, res) => {
res.statusCode = 500
- // Note: err.message could expose sensitive information in production
- res.end(err.message)
+ if (process.env.NODE_ENV === 'production') {
+ res.end('Internal Server Error')
+ } else {
+ res.end(err.message)
+ }
}
/**
@@ -87,8 +90,10 @@ module.exports = (config = {}) => {
if (firstMiddleware?.id) {
// Cache router -> pattern relation for URL pattern replacement in nested routing
// This enables efficient URL rewriting when entering nested router contexts
- const { pattern } = parse(prefix, true)
- routers[firstMiddleware.id] = pattern
+ const { pattern, keys } = parse(prefix, true)
+ routers[firstMiddleware.id] = keys.length === 0 && prefix.indexOf('*') === -1 // No params and no wildcards
+ ? prefix.length // Static match
+ : pattern // Regex match
}
return router // Ensure chainable API by returning router instance
@@ -157,12 +162,7 @@ module.exports = (config = {}) => {
} else if (params) {
// Manual property copying - optimized for small objects
// Pre-compute keys and length to avoid repeated calls
- const paramKeys = Object.keys(params)
- let i = paramKeys.length
- while (i--) {
- const key = paramKeys[i]
- req.params[key] = params[key]
- }
+ Object.assign(req.params, params)
}
return next(middlewares, req, res, 0, routers, defaultRoute, errorHandler)
diff --git a/package.json b/package.json
index fc6c91c..ef062c2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "0http",
- "version": "4.3.0",
+ "version": "4.4.0",
"description": "Zero friction HTTP request router. The need for speed!",
"main": "index.js",
"scripts": {
@@ -52,4 +52,4 @@
"trouter": "^4.0.0"
},
"types": "./index.d.ts"
-}
+}
\ No newline at end of file
diff --git a/tests/v4.4.test.js b/tests/v4.4.test.js
new file mode 100644
index 0000000..abae0ec
--- /dev/null
+++ b/tests/v4.4.test.js
@@ -0,0 +1,97 @@
+/* global describe, it, before, after */
+const cero = require('../index')
+const request = require('supertest')
+
+describe('v4.4 Improvements', () => {
+ describe('Security: Default Error Handler', () => {
+ let originalEnv
+
+ before(() => {
+ originalEnv = process.env.NODE_ENV
+ })
+
+ after(() => {
+ process.env.NODE_ENV = originalEnv
+ })
+
+ it('should hide error message in production', async () => {
+ process.env.NODE_ENV = 'production'
+ const { router, server } = cero()
+
+ router.get('/error', (req, res, next) => {
+ next(new Error('Sensitive Info'))
+ })
+
+ await request(server)
+ .get('/error')
+ .expect(500)
+ .expect('Internal Server Error')
+ })
+
+ it('should show error message in development', async () => {
+ process.env.NODE_ENV = 'development'
+ const { router, server } = cero()
+
+ router.get('/error', (req, res, next) => {
+ next(new Error('Sensitive Info'))
+ })
+
+ await request(server)
+ .get('/error')
+ .expect(500)
+ .expect('Sensitive Info')
+ })
+ })
+
+ describe('Performance: Static Nested Routes', () => {
+ it('should handle static nested routes correctly', async () => {
+ const { router, server } = cero()
+ const nestedRouter = cero().router
+
+ nestedRouter.get('/world', (req, res) => {
+ res.end('Hello World')
+ })
+
+ router.use('/hello', nestedRouter)
+
+ await request(server)
+ .get('/hello/world')
+ .expect(200)
+ .expect('Hello World')
+ })
+
+ it('should handle deep static nested routes', async () => {
+ const { router, server } = cero()
+ const r1 = cero().router
+ const r2 = cero().router
+
+ r2.get('/end', (req, res) => {
+ res.end('End')
+ })
+
+ r1.use('/level2', r2)
+ router.use('/level1', r1)
+
+ await request(server)
+ .get('/level1/level2/end')
+ .expect(200)
+ .expect('End')
+ })
+
+ it('should still handle regex nested routes', async () => {
+ const { router, server } = cero()
+ const nestedRouter = cero().router
+
+ nestedRouter.get('/world', (req, res) => {
+ res.end('Hello World')
+ })
+
+ router.use('/hello/:name', nestedRouter)
+
+ await request(server)
+ .get('/hello/john/world')
+ .expect(200)
+ .expect('Hello World')
+ })
+ })
+})