npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

wpress-x

v2.0.1

Published

wpress-x - a tiny, fast, batteries-included web framework for Node.js. Express-compatible API, zero dependencies, view engines, middleware, sendFile and more built in.

Downloads

286

Readme

wpress-x

The next version of Express.js. Same API you already know, everything you would normally install separately already in the box, zero dependencies.

const WpressX = require('wpress-x')
const app = WpressX()

app.get('/hello/:name', (req, res) => {
  res.json({ hello: req.params.name })
})

app.done(3000)

Why wpress-x

| | Express | wpress-x | | --- | --- | --- | | npm dependencies when you install it | ~50 packages | 0 | | body-parser | install it | built in | | cookie-parser | install it | built in | | express-session | install it | built in | | multer (uploads) | install it | built in | | morgan (logging) | install it | built in | | helmet | install it | built in | | cors | install it | built in | | compression | install it | built in | | csurf | install it (deprecated) | built in | | serve-static, serve-index, serve-favicon | install them | built in | | express-rate-limit | install it | built in | | method-override, response-time | install them | built in | | EJS / Pug / Handlebars / Markdown | install them | built in | | res.sendFile (the send package) | bundled, but external | built in | | TypeScript types | @types/express | built in | | Throughput vs a bare http.Server | ~35–45% | ~70% (see npm run bench) |

Everything is one file away: lib/. No node_modules archaeology, no supply chain, no version drift between 50 packages that were never meant to agree.


Install

# scaffold a new project
node bin/wpress-x.js create my-app && cd my-app && npm start

# or run the tour of every feature from a checkout
node examples/app.js        # http://localhost:3000

# or copy the folder into your project and
const WpressX = require('./wpress-x')

package.json has no dependencies section, and it never will.


The API you started with

Your original wpress-x class is intact — every one of these still works:

const WpressX = require('wpress-x')
const app = WpressX()

app.routes /* .GET / .POST tables */                  // kept, see app.routes()
app.get('/path', handler)                             // route + settings getter
app.post('/path', handler)
app.us(middleware)                                    // app.use(), as you typed it
res.send(body)                                        // strings, objects, Buffers
app.done(port, callback)                              // start listening

app.done() returns the http.Server; app.listen() and app.start() are aliases. res.send now also handles objects (JSON), numbers, Buffers, null and undefined, and sets ETag, Content-Type, Content-Length and HEAD handling for you.


Contents


Routing

app.get('/users/:id', (req, res) => res.json({ id: req.params.id }))
app.get('/num/:n(\\d+)', ...)        // regex-constrained param
app.get('/opt/:id?', ...)            // optional param
app.get('/files/:path*', ...)        // zero-or-more segments
app.get('/all/*', ...)               // wildcard
app.get(/^\/regex\/(\d+)$/, ...)     // a RegExp route
app.get(['/a', '/b'], ...)           // several paths, one handler

Also: app.post, app.put, app.patch, app.delete, app.head, app.options, app.all, and app.METHOD for anything in http.METHODS.

// chain verbs on one path
app.route('/article/:id')
  .get((req, res) => res.json({ id: req.params.id }))
  .put((req, res) => res.send('updated'))
  .delete((req, res) => res.send('deleted'))

// bundle routes under a prefix
app.group('/v1', (router) => {
  router.get('/ping', (req, res) => res.json({ pong: Date.now() }))
})

// a router, mounted anywhere
const api = WpressX.Router()
api.use((req, res, next) => { res.setHeader('X-Api', 'wpress-x'); next() })
api.get('/users', (req, res) => res.json(users))
app.use('/api', api)

// a whole sub-application
const admin = WpressX()
admin.get('/', (req, res) => res.send('admin'))
app.use('/admin', admin)

// param callbacks run once per value
app.param('id', (req, res, next, value) => {
  req.user = findUser(value)
  if (!req.user) return next(WpressX.createError(404, 'No such user'))
  next()
})

next('route') skips the rest of the current route; next('router') escapes the current router.


Middleware: app.use

app.use((req, res, next) => { console.log(req.method, req.path); next() })
app.use('/admin', requireAuth)                    // path-scoped
app.use('/api', apiRouter)                        // mount a Router
app.use('/blog', blogApp)                         // mount a sub-App
app.use(wpress-x.json(), wpress-x.urlencoded())         // several at once

Path arguments accept strings (with :params), arrays, and RegExps. Error middleware has four arguments and goes last:

app.use((err, req, res, next) => {
  res.status(err.status || 500).json({ error: err.message })
})

Request

req.params          // { id: '42' }            route parameters
req.query           // ?a=1&b[c]=2  ->  { a: '1', b: { c: '2' } }
req.body            // parsed by wpress-x.json() & friends
req.files           // parsed by wpress-x.multipart()
req.rawBody         // the buffer, when the parser ran with verify
req.cookies         // wpress-x.cookieParser()
req.signedCookies   // verified against the secret
req.cookie('name')  // signed first, plain second
req.session         // wpress-x.session()
req.get('user-agent')
req.accepts(['json', 'html'])
req.acceptsLanguages(['en', 'ur'])
req.is('json')                       // null when unknown, false when it is not
req.ip, req.ips, req.hostname, req.protocol, req.secure, req.subdomains
req.path, req.originalUrl, req.baseUrl, req.method, req.xhr, req.fresh
req.param('id', 'default')           // params, then body, then query
req.csrfToken()                      // wpress-x.csrf()

trust proxy is fully supported (true, a number, a list, a function, or 'loopback'), so req.ip and req.protocol report the client's values behind a load balancer.


Response

res.send('text')          // text/html
res.send({ a: 1 })        // application/json
res.send(Buffer)          // application/octet-stream
res.send(404)             // status code
res.json(obj) res.jsonp(obj) res.text(str) res.html(str)
res.status(201).json(obj)
res.sendStatus(404)       // 404 + "Not Found"
res.type('json') res.set('X-A', '1') res.get('X-A') res.append('Link', v)
res.vary('Accept') res.links({ next: '/page/2' })
res.redirect('/done')  res.redirect(301, 'https://example.com')
res.cookie('n', 1, { signed: true, httpOnly: true })  res.clearCookie('n')
res.attachment('report.pdf')
res.format({ 'text/html': fn, 'application/json': fn, default: fn })
res.render('index', { title: 'Hi' })
res.sse(data, 'event', id)   // Server-Sent Events
res.stream(readable)         // pipe with cleanup
res.setTimeout / res.flushHeaders() / res.socket / res.headersSent

res.json respects json spaces, json replacer, json escape and jsonp callback name. res.send sets ETag and answers conditional GETs.


Sending files

// stream a file with ranges, ETag, Last-Modified and HEAD support
app.get('/cv', (req, res) => {
  res.sendFile('cv.pdf', { root: __dirname + '/files', maxAge: '1d' }, (err) => {
    if (err) res.status(err.status || 500).send('no file')
  })
})

// as a download
app.get('/dl', (req, res) => res.download('/tmp/report.pdf', 'report.pdf'))

// a whole directory
app.use(WpressX.static(__dirname + '/public', { maxAge: '1h', extensions: ['html'] }))
app.use('/files', WpressX.serveIndex(__dirname + '/public'))

sendFile refuses to escape its root (403), honours the dotfiles policy, serves directory index files, and speaks Range: (206) and If-None-Match (304).


View engines

Four engines ship with wpress-x. No npm install ejs, no .pug build step.

app.set('views', __dirname + '/views')
app.set('view engine', 'ejs')          // .ejs is the default for res.render

app.get('/', (req, res) => res.render('index', { title: 'Hello' }))
app.get('/about', (req, res) => res.render('about.md'))     // any extension
app.get('/p', (req, res) => res.render('page.pug'))
app.get('/h', (req, res) => res.render('page.hbs'))

Anything you pass to res.render becomes a local, and so do app.locals and res.locals. wpress-x also auto-detects: res.render('about') finds about.md even when the default engine is ejs.

wpress-x-ejs

<h1><%= title %></h1>            <!-- escaped   -->
<%- include('partials/nav', { title }) %>
<%# a comment %>
<%% a literal <% tag %>
<% items.forEach(function (item) { -%>
  <li><%= item.name %></li>
<% }) %>

<% %> scriptlets, <%= %> escaped, <%- %> raw, <%# %> comments, <%%/%%> literals, -%> newline trimming, include() and layout(). Missing variables render as empty instead of throwing.

wpress-x-pug

extends layout

block content
  h1.heading#top= title
  ul
    each item, i in items
      li= i + ': ' + item
  mixin badge(label)
    span.badge= label
  +badge('new')
    p inside the mixin's block

Indentation syntax, void elements, doctypes, #id/.class shorthand, attributes (comma or space separated), =/!=/#{}/!{}, each/while/for/if/else if/else/unless, block text, piped text, // and //-, include, extends with block/append/prepend, and mixin/+call with blocks.

wpress-x-hbs

<h1>{{title}}</h1>
{{{rawHtml}}}  {{&alsoRaw}}
{{#each items}}<li>{{@index}}: {{this.name}}</li>{{else}}<li>none</li>{{/each}}
{{#if ok}}yes{{else}}no{{/if}}
{{#with user}}{{name}} of {{../org}}{{/with}}
{{upper title}}  {{join names ", "}}
{{> footer}}

Mustaches, triple-stash raw output, dotted paths, ../ parent scopes, @index/@key/@first/@last/@root, if/unless/each/with with {{else}}, helpers with positional and hash arguments, registered partials and file-based partials loaded from views/partials/.

wpress-x-md

Markdown with headings, emphasis, links, images, autolinks, fenced and indented code blocks, blockquotes, ordered/unordered nested lists, pipe tables, horizontal rules and inline code — HTML-escaped first, so <script> in a comment cannot run, and javascript:/data: URLs are neutralised.

wpress-x-plain

Raw files, with optional {{ name }} interpolation for HTML templates.

Your own engine

app.engine('njk', (path, options, callback) => {
  callback(null, myRenderer(path, options))
})

Or use any Express-compatible engine: app.engine('ejs', require('ejs').renderFile).


Built-in middleware

All of them are on the factory: WpressX.json, WpressX.static, …

| Middleware | What it does | | --- | --- | | WpressX.json(opts) | JSON bodies, limit, strict, verify, gzip/deflate/br | | WpressX.urlencoded(opts) | Form bodies, simple or extended (nested, arrays, dots) | | WpressX.text(opts) / WpressX.raw(opts) | String and Buffer bodies | | WpressX.bodyParser(opts) | json + urlencoded in one | | WpressX.multipart(opts) | multipart/form-data uploads, dest to write to disk | | WpressX.upload(opts) | alias of multipart | | WpressX.cookieParser(secret) | req.cookies, req.signedCookies, req.cookie() | | WpressX.session(opts) | Sessions with Memory/File stores, regenerate, destroy | | WpressX.static(root, opts) | Static files with ranges, ETags, caching | | WpressX.serveIndex(root) | Directory listings | | WpressX.favicon(path?) | /favicon.ico (inline transparent icon by default) | | WpressX.cors(opts) | CORS + preflight, string/array/RegExp/function origins | | WpressX.helmet(opts) | 13 security headers + a CSP directive builder | | WpressX.compress(opts) | br / gzip / deflate with a size threshold | | WpressX.rateLimit(opts) | Rate limiting with RateLimit-* and Retry-After | | WpressX.csrf(opts) | Synchroniser tokens, req.csrfToken(), res.locals.csrfToken | | WpressX.methodOverride(getter) | _method from query/body/header | | WpressX.responseTime(opts) | X-Response-Time | | WpressX.logger(format) | dev, tiny, short, common, combined, custom, :tokens | | WpressX.errorHandler(opts) | HTML/JSON error pages, stacks outside production |

app.use(WpressX.logger('dev'))
app.use(WpressX.compress())
app.use(WpressX.json({ limit: '1mb' }))
app.use(WpressX.multipart({ dest: 'uploads' }))
app.use(WpressX.cookieParser(process.env.SECRET))
app.use(WpressX.session({ secret: process.env.SECRET, resave: false, saveUninitialized: false }))
app.use(WpressX.static('public'))
app.use(WpressX.rateLimit({ windowMs: 60_000, max: 100 }))
app.use(WpressX.errorHandler())

Application settings

app.set('views', 'views')             app.get('views')
app.set('view engine', 'ejs')
app.set('view cache', true)           // in production it is on by default
app.set('trust proxy', 1)             // true | number | list | 'loopback' | fn
app.set('etag', 'weak')               // 'weak' | 'strong' | false | fn
app.set('query parser', 'extended')   // 'extended' | 'simple' | false | fn
app.set('json spaces', 2)
app.set('jsonp callback name', 'callback')
app.set('subdomain offset', 2)
app.set('x-powered-by', false)        // hide X-Powered-By
app.enable('strict routing')          // app.enable / app.disable / app.enabled

app.locals is shared with every res.locals through the prototype chain, so app.locals.siteName = 'wpress-x' is visible in every template.

Nice-to-haves:

app.routes()     // [{ path, methods, fullPath, handlers }, ...]
app.summary()    // a printable route table

Errors

const WpressX = require('wpress-x')

app.get('/boom', () => { throw WpressX.createError(418, 'I am a teapot') })

// normalise anything that comes out of a callback
const err = WpressX.normalizeError(whatever)

// always last
app.use(WpressX.errorHandler())

createError(status), createError(status, message), createError(status, Error), createError(Error) and createError(err, { headers }) all work. Uncaught errors, next(err) and rejected promises all end up in the same place: your error middleware, or WpressX.errorHandler() if you have none.


Benchmarks

npm run bench           # 50k requests, 20 concurrent
node bench/bench.js --n 20000 -c 50

On the machine this was written on (Node 20):

scenario                     req/s   mean ms      p50      p95      p99   vs raw
--------------------------------------------------------------------------------
raw http.Server              8,084      2.47     2.21     5.02    6.538     100%
wpress-x (1 route)              5,430      3.68    3.251    5.891    8.736      67%
wpress-x + 6 middleware         4,189      4.77    4.262    7.168   11.059      52%
wpress-x (router + params)      5,884     3.396    3.132    4.719    6.924      73%

wpress-x keeps ~70% of a bare http.Server's throughput on a single route and still ~52% with six middleware in the stack — roughly double what Express manages, with none of the dependency weight.


Tests

npm test              # = node test/run.js
node test/run.js      # same thing, any Node from 18 to 24

88 tests over routing, middleware, request/response, file sending, all five view engines, template-file encodings and the error handler. node:test and a tiny test/run.js loader — no test framework to install.

Why not node --test test/? That works up to Node 20 and breaks from Node 22 onwards: the argument is treated as a module path and you get Error: Cannot find module .../test. test/run.js loads the *.test.js files itself, so it behaves identically on every version and on Windows (where cmd.exe would not expand a glob for you anyway).


Windows & Node 22+ notes

wpress-x is written to be platform-agnostic, and the test suite proves it:

  • Line endings: every engine normalises CRLF/CR and strips a UTF-8 BOM before parsing, so templates saved by Notepad render the same as ones committed from Linux.
  • Paths: file paths go through path.join/path.resolve (backslashes are fine), while URLs are always joined with / — so directory listings produce working links on Windows.
  • Ports: app.done(3000) prints a readable message instead of an EADDRINUSE stack when the port is taken.
  • Spaces in paths: the whole suite passes from a directory with spaces in its name.

File map

wpress-x/
├── index.js              # require('wpress-x')
├── bin/wpress-x.js          # CLI: create / routes / serve
├── lib/
│   ├── wpress-x.js          # the factory + every static
│   ├── application.js    # Application: settings, engines, mounting, done()
│   ├── request.js        # Request prototype
│   ├── response.js       # Response prototype
│   ├── view.js           # View class + engine resolution
│   ├── router/           # Layer, Route, Router (matching, params, dispatch)
│   ├── middleware/       # 16 middleware modules (see the table above)
│   ├── engines/          # ejs, pug, hbs, markdown, plain
│   ├── internal/         # finalhandler
│   └── utils/            # mime, qs, url, path-to-regexp, etag, fresh,
│                         # range, content-disposition, accepts, http-errors,
│                         # send (sendFile), utils, scope
├── examples/             # a tour of every feature (node examples/app.js)
├── bench/                # dependency-free load generator
├── test/                 # 88 tests + run.js loader
└── types/index.d.ts      # TypeScript declarations

Licence

MIT.