@mila.solutions/express
v5.2.1-mila.0
Published
Fast, unopinionated, minimalist web framework (Mila fork with fast-json-stringify)
Downloads
80
Maintainers
Readme
@mila.solutions/express
Performance-focused fork of Express 5.2.1 with integrated fast-json-stringify and internal optimizations.
100% compatible with Express 5 — all 1,342 tests pass. Drop-in replacement.
What's different
| Feature | Express 5.2.1 | @mila.solutions/express |
|---|---|---|
| JSON serialization | JSON.stringify | fast-json-stringify (pre-compiled via JSON Schema) |
| Route-level schemas | No | Yes — per-route, per-status-code |
| res.locals | Allocated every request | Lazy — only allocated on first access |
| Error handler binding | New closure per request | Pre-bound at init |
| req.fresh check | Always runs | Short-circuited for non-conditional requests |
| ETag on JSON | Always | Configurable via json etag setting |
Benchmark results (10K requests, 20 concurrent)
Express 5.2.1 (original) ███████████████████████████████████ 6,647 rps
@mila (no schema) ████████████████████████████████████████ 7,577 rps (+14%)
@mila (schema) ███████████████████████████████████████ 7,377 rps (+11%)
@mila (schema + no etag) ███████████████████████████████████████ 7,414 rps (+12%)+10-19% faster depending on configuration. Improvement comes from reduced allocations per request and pre-compiled JSON serialization.
Installation
npm install @mila.solutions/expressQuick start
Works exactly like Express:
const express = require('@mila.solutions/express')
const app = express()
app.get('/users', (req, res) => {
res.json({ id: 1, name: 'John' })
})
app.listen(3000)Schema-based JSON serialization
Define a JSON Schema on your route to enable fast-json-stringify. The schema is compiled once at startup and reused for every request.
Basic usage
const express = require('@mila.solutions/express')
const app = express()
app.get('/user', {
schema: {
response: {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' },
email: { type: 'string' }
}
}
}
}, (req, res) => {
res.json({ id: 1, name: 'John', email: '[email protected]' })
})Per-status-code schemas
app.post('/users', {
schema: {
response: {
201: {
type: 'object',
properties: {
id: { type: 'integer' },
name: { type: 'string' }
}
},
400: {
type: 'object',
properties: {
error: { type: 'string' },
details: { type: 'array', items: { type: 'string' } }
}
}
}
}
}, (req, res) => {
// Schema for 201 is used automatically
res.status(201).json({ id: 1, name: 'John' })
})Generic status code groups
Use 2xx, 3xx, 4xx, 5xx to match any status code in a range:
app.get('/data', {
schema: {
response: {
'2xx': {
type: 'object',
properties: {
data: { type: 'string' }
}
},
'4xx': {
type: 'object',
properties: {
error: { type: 'string' }
}
}
}
}
}, handler)Resolution order
When res.json() is called, the serializer is resolved in this order:
- Exact match — status code matches exactly (e.g.
200) - Generic match — status code group matches (e.g.
2xx) - Default — a plain schema object (no status code keys)
- Fallback — standard
JSON.stringify
App-level schemas
Set a default schema for all routes:
app.set('json schema', {
type: 'object',
properties: {
data: {},
meta: {}
}
})Route-level schemas always take priority over app-level.
Additional settings
json etag
Disable ETag generation for JSON responses:
app.set('json etag', false)This skips the etag computation on every res.json() call, which saves CPU when your clients don't use conditional requests (If-None-Match).
How it works
fast-json-stringify
Standard JSON.stringify inspects every value at runtime to determine its type. fast-json-stringify uses JSON Schema to generate a specialized serializer function at startup that knows the exact shape of your data. This avoids type checking per-value and produces strings ~1.5x faster for typical API payloads.
Schemas are compiled once and cached via WeakMap, so the same schema object always returns the same pre-compiled serializer.
Internal optimizations
These apply to all requests, even without schemas:
- Pre-bound error handler —
logerroris bound once atapp.init()instead of creating a new closure per request inapp.handle() - Lazy
res.locals— Uses a self-replacing getter. The object is only created when your code actually accessesres.locals, saving an allocation on routes that don't use it req.freshshort-circuit — Skips the full freshness check when the request has noIf-None-MatchorIf-Modified-Sinceheaders- Buffer fast-path in
_sendJson()— Pre-computes Content-Length from the buffer byte length instead of going through the fullres.send()path
Compatibility
- Based on Express 5.2.1
- Requires Node.js >= 18
- All 1,342 Express tests pass
- Drop-in replacement — no API changes for existing code
- Schemas are opt-in; without them, it behaves identically to Express
Running tests
npm install
npm testRunning benchmarks
# A/B comparison (Express original vs @mila)
node benchmarks/compare.js --requests 10000
# Scaling test
node benchmarks/scale.jsLicense
Based on Express by the Express.js community.
