Nano's Wildcard Routing: Simplify URL Matching Without Middleware
Nano's built-in wildcard routing lets you match patterns like /api/* and /user/:id without extra middleware. This blog shows a minimal example, explains the compile‑once regex approach, and highlights ordering rules and the lack of body parsing.
29 Oct 2026, 02:13 UTC

The Problem: Overkill for Simple Route Matching
Many Node.js projects pull in Express or a dedicated router just to handle a handful of wildcard paths like /api/* or /user/:id. That adds dependencies, middleware chains, and cognitive overhead for a feature that can be surprisingly minimal.
Thesis: Nano's Built‑in Wildcard Routing Is Often Enough
Nano, a deliberately tiny HTTP server, compiles wildcard and parameterized patterns into regular expressions at startup. You get concise URL matching with zero extra packages—provided you understand its ordering rules and the few things it deliberately doesn’t do.
How Nano’s Wildcard Routing Works
When you call nano.get('/api/*', handler) or nano.get('/user/:id', handler), the library translates the pattern into a RegExp once, during server initialization. The compiled regex is stored in an internal route table. At request time, Nano walks that table in registration order and invokes the first matching handler. Because matching happens before any static‑file fallback, you retain explicit control over request flow.
Key characteristics:
- Pattern syntax:
*matches any segment (non‑greedy),:namecaptures a named parameter. - Compilation: Done once at startup; no per‑request regex construction.
- Order matters: Routes are evaluated in the order you register them. A broad wildcard placed before a specific route will capture requests intended for the latter.
Minimal Working Example
Create a fresh directory, install Nano, and add the following server.js. The example registers a parameterized route, a wildcard route, and a static‑file fallback—all in under 20 lines.
// server.js
const nano = require('nano')()
// 1. Specific parameterized route
nano.get('/user/:id', (req, res) => {
res.json({ userId: req.params.id })
})
// 2. Wildcard route – must come AFTER specific routes
nano.get('/api/*', (req, res) => {
res.json({ apiPath: req.params[0] }) // captured wildcard segment
})
// 3. Static file serving (built‑in)
nano.static('public')
// Start server – default port 8080 in Nano 2.x
nano.listen()
console.log('Nano listening on http://localhost:8080')
Run It
- Install Node.js (≥12.x) – verify with
node -v. - Initialize a project and install Nano:
mkdir nano-demo && cd nano-demo npm init -y npm install nano - Create
public/index.html(optional) to test static serving.mkdir public echo 'Static file' > public/index.html - Start the server:
node server.js - Verify routes (use curl, httpie, or a browser):
GET http://localhost:8080/user/42→{ "userId": "42" }GET http://localhost:8080/api/v1/resource→{ "apiPath": "v1/resource" }GET http://localhost:8080/→ servespublic/index.html
Expected checks: Console shows “Nano listening on http://localhost:8080”. No error stack traces. Each request returns the JSON or HTML described above.
Trade‑offs and Limitations
Route Ordering Is Manual
Nano does not auto‑sort routes by specificity. If you register /api/* before /user/:id, a request to /user/42 will never reach the parameterized handler because the wildcard matches first. Always place specific routes before broad wildcards.
No Built‑in Body Parsing
POST/PUT handlers receive req.body as a raw Buffer. You must parse JSON, form data, or multipart yourself (e.g., with body-parser or a tiny custom middleware). This keeps Nano small but shifts responsibility to you.
Missing Production Hardening
Nano provides no rate limiting, security headers, or structured error handling. For production, layer a reverse proxy (nginx, Caddy) or add middleware packages explicitly.
Version Note
Nano 1.x and 2.x share the same routing API. The only breaking default is the port: 1.x used 3000, 2.x uses 8080. Pin a major version in package.json if you rely on the default port.
Verify and Extend
After the example runs, try these quick experiments to cement understanding:
- Swap the two
nano.getcalls and observe the wildcard capturing/user/42. - Send a POST to
/api/testwithcurl -X POST -d '{"foo":"bar"}' -H "Content-Type: application/json" http://localhost:8080/api/testand logreq.bodyto see the raw Buffer. - Add a tiny JSON parser middleware before the routes:
nano.use((req, res, next) => { let data = '' req.on('data', chunk => data += chunk) req.on('end', () => { try { req.body = JSON.parse(data) } catch { req.body = {} } next() }) })
These steps confirm the routing behavior, expose the body‑parsing gap, and show how to extend Nano without pulling in a full framework.
Takeaway
If your service needs only a few wildcard or parameterized routes, Nano’s native routing eliminates a dependency and keeps the request pipeline transparent. Just remember: register specific routes first, parse bodies yourself, and harden the stack for production. Clone the example, tweak the patterns, and you’ll have a working router in minutes.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.