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

arcara

v0.1.13

Published

A TypeScript-native, zero-runtime-dependency Node.js HTTP framework

Downloads

1,414

Readme

Arcara

Website npm version npm downloads license

A TypeScript-first, zero-runtime-dependency Node.js HTTP framework.

Radix-tree routing. Full type inference. Minimal, composable middleware.

No setup. No config. Just import and go.


Why Arcara?

  • ⚡ Radix-tree routing (fast path matching)
  • 🧠 Full type inference for route params (no generics, no casting)
  • 🪶 Zero runtime dependencies
  • 🔌 Minimal, fully composable middleware model
  • 📦 No config, no code generation

If you like Express but want stronger typing and less overhead, Arcara is a natural upgrade.


Requirements

  • Node.js 18+

Install

npm install arcara

Node types are included — no @types/node, no tsconfig changes required.


Quick Start

import { Arcara, HttpError } from 'arcara';

const app = new Arcara();

app.use((req, _res, next) => {
  console.log(req.method, req.url);
  next();
});

app.get('/users/:id', (req, res) => {
  res.json({ id: req.params.id });
  // req.params.id is inferred as string — no casting needed
});

app.post('/users', (req, res) => {
  res.status(201).json(req.body);
  // req.body is parsed JSON for POST/PUT/PATCH (application/json)
});

app.onError((err, _req, res) => {
  res.status(err.status).json({ error: err.message });
  // err is always normalized to HttpError internally
});

app.listen(3000, () => console.log('Listening on :3000'));

API

Application

import { Arcara } from 'arcara';

const app = new Arcara();

app.listen(3000);
// start server on port 3000

app.listen(3000, '0.0.0.0', () => {});
// with host + optional callback

Routing

app.get(path, ...handlers);
app.post(path, ...handlers);
app.put(path, ...handlers);
app.patch(path, ...handlers);
app.delete(path, ...handlers);

Path params are inferred from the route string:

app.get('/orgs/:orgId/repos/:repoId', (req, res) => {
  const { orgId, repoId } = req.params;
  // both are typed as string
});

Handlers

type Handler = (
  req,
  res,
  next?,
) => void | ArcaraResponse | Promise<void | ArcaraResponse>;
app.get('/ping', (req, res) => {
  return res.json({ ok: true });
  // returning is optional but supported
});

app.get('/data', async (req, res) => {
  const data = await fetchSomething();
  return res.json(data);
  // works the same in async handlers
});

Middleware

app.use(handler);
// runs on every request

app.use('/prefix', handler);
// runs only when URL starts with /prefix

app.use('/prefix', router);
// mount a sub-router under /prefix

The prefix is stripped from req.url before your handler sees it. // a middleware mounted at /static receives /logo.png, not /static/logo.png


Sub-Routers

import { Router } from 'arcara';

const users = new Router();

users.get('/:id', (req, res) => {
  res.json({ id: req.params.id });
});

users.post('/', (req, res) => {
  res.status(201).json(req.body);
});

users.delete('/:id', (req, res) => {
  res.status(204).end();
});

app.use('/users', users);
// /users/:id → handled inside the router as /:id

Routers can be nested infinitely.


Response Helpers

res.status(201).json({ created: true });
// sets status + JSON + ends response

res.send('plain text');
// auto Content-Type + Content-Length

res.send(buffer);
// Buffer / Uint8Array / ArrayBuffer supported

res.send({ key: 'value' });
// object → JSON automatically

Error Handling

app.onError((err, req, res) => {
  res.status(err.status).json({ error: err.message });
});

Ways to trigger the error handler:

throw new HttpError(400, 'Bad request');
// explicit error with status + message

next(new HttpError(400, 'Bad request'));
// pass error to the pipeline

return res.status(400).json({ error: 'Bad request' });
// bypass error handler entirely
throw new HttpError(422, 'Validation failed', {
  field: 'email',
  reason: 'invalid format',
});
// optional third argument for extra details

Scoped error handling per router:

const api = new Router();

api.onError((err, _req, res) => {
  res.status(err.status).json({ error: err.message, code: err.status });
});

app.use('/api', api);

Request Behavior

| Feature | Behavior | | ------------ | ------------------------------------------------------ | | req.body | Parsed for JSON (application/json) on POST/PUT/PATCH | | req.params | Fully typed from route string | | HEAD | Automatically handled via GET | | 405 | Returned when path exists but method does not |


Static Files

import { serveStatic } from 'arcara';

app.use(serveStatic('./public'));
// serves at root → /logo.png maps to ./public/logo.png

app.use('/static', serveStatic('./assets'));
// mount via router → prefix stripped before serveStatic sees req.url

app.use(serveStatic('public', { index: 'app.html' }));
// use app.html instead of index.html when accessing a directory (e.g. / → app.html)

Features:

  • Safe path resolution (prevents directory traversal)
  • Directory index fallback (/about/about/index.html)
  • MIME type detection (with fallback for unknown extensions)
  • ETag / Last-Modified headers (304 Not Modified support)
  • HEAD request handling
  • Streaming via pipeline (efficient file serving)

Example

const requireAuth = (req, res, next) => {
  const token = req.headers.authorization?.replace('Bearer ', '');

  if (!token) {
    throw new HttpError(401, 'Unauthorized');
  }

  req.user = verifyToken(token);
  next();
};

app.use('/api', requireAuth);
// all /api routes require authentication

app.get('/api/profile', (req, res) => {
  res.json({ user: req.user });
});

Contributing

Open an issue before submitting large changes. Keep PRs focused, include tests, and follow the existing style.


License

MIT © 2026 Ala Ben Aissia