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

acl-next

v1.0.0

Published

Modern TypeScript Access Control List (RBAC) with Redis, MongoDB and in-memory backends. Fork of optimalbits/node_acl.

Readme

acl-next

Modern TypeScript Access Control Lists (ACL / RBAC) for Node.js — with Redis, MongoDB and in-memory backends, and Express middleware.

A maintained, modernized fork of optimalbits/node_acl (MIT). Same proven model — users → roles → resources → permissions, with role hierarchies — rebuilt as TypeScript, promise-native, and zero runtime dependencies.

What changed from node_acl

  • TypeScript, with full type declarations.
  • Promise-only API — the legacy callback signatures are gone (use await).
  • No runtime dependencies. bluebird, lodash and async are removed. redis and mongodb are now optional peer dependencies — install only the driver you use.
  • Modern drivers: redis v4+, mongodb v4+.
  • IDs are normalized to strings in stored/returned values.
  • Dual ESM + CommonJS build.

See the Migration guide below.

Install

npm install acl-next
# plus the backend driver you use (optional peer deps):
npm install redis        # for RedisBackend
npm install mongodb      # for MongoDBBackend
# MemoryBackend needs nothing

Quick start

import { Acl, MemoryBackend } from "acl-next";

const acl = new Acl(new MemoryBackend());

// Roles get permissions over resources (roles/resources created implicitly):
await acl.allow("guest", "blogs", "view");
await acl.allow("member", "blogs", ["edit", "view", "delete"]);

// Users get roles (users created implicitly):
await acl.addUserRoles("joed", "guest");

// Query:
await acl.isAllowed("joed", "blogs", "view"); // => true
await acl.isAllowed("joed", "blogs", "edit"); // => false

Role hierarchies

await acl.addRoleParents("baz", ["foo", "bar"]); // baz inherits foo + bar

Bulk permissions

await acl.allow([
  {
    roles: ["guest", "member"],
    allows: [
      { resources: "blogs", permissions: "get" },
      { resources: ["forums", "news"], permissions: ["get", "put", "delete"] },
    ],
  },
]);

Wildcard

await acl.allow("admin", ["blogs", "forums"], "*"); // all permissions

Backends

import { Acl, RedisBackend, MongoDBBackend, MemoryBackend } from "acl-next";

// In-memory (no deps) — great for tests / single process:
new Acl(new MemoryBackend());

// Redis (node-redis v4+):
import { createClient } from "redis";
const redis = createClient();
await redis.connect();
new Acl(new RedisBackend(redis, "acl" /* key prefix */));

// MongoDB (mongodb v4+):
import { MongoClient } from "mongodb";
const client = new MongoClient("mongodb://localhost:27017");
await client.connect();
new Acl(new MongoDBBackend(client.db("mydb"), { prefix: "acl_", useSingle: false }));

Bring your own backend by implementing the Backend<T> interface (see src/types.ts).

Express middleware

import { aclErrorHandler } from "acl-next";

// Protect a route — resource defaults to req.url, permission to req.method:
app.put("/blogs/:id", acl.middleware(), handler);

// Only the first N path components form the resource name:
app.put("/blogs/:id/comments/:commentId", acl.middleware(3), handler);

// Custom userId (value or resolver) and explicit permission:
app.put("/blogs/:id", acl.middleware(3, (req) => req.user.id, "post"), handler);

// Render the 401/403 errors it raises:
app.use(aclErrorHandler("json")); // or "html", or omit for plain text

The middleware resolves the user from (in order): the userId argument, req.session.userId, then req.user.id.

API

All methods return Promises.

| Method | Description | | --- | --- | | addUserRoles(userId, roles) | Assign role(s) to a user | | removeUserRoles(userId, roles) | Remove role(s) from a user | | userRoles(userId) | Roles assigned to a user | | roleUsers(role) | Users that have a role | | hasRole(userId, role) | Whether a user has a role | | addRoleParents(role, parents) | Add parent role(s) (inheritance) | | removeRoleParents(role, parents?) | Remove parent role(s) (all if omitted) | | removeRole(role) | Remove a role and its permissions | | removeResource(resource) | Remove a resource | | allow(roles, resources, permissions) / allow(rules[]) | Grant permissions | | removeAllow(role, resources, permissions?) | Revoke permissions | | allowedPermissions(userId, resources) | Map of resource → permissions for a user | | isAllowed(userId, resource, permissions) | Whether a user has all permissions | | areAnyRolesAllowed(roles, resource, permissions) | Whether any role qualifies | | whatResources(roles) / whatResources(roles, permissions) | Resources a role can access | | middleware(numPathComponents?, userId?, actions?) | Express middleware factory |

Migration from node_acl

  1. Rename the import: aclacl-next.

  2. Drop callbacks, use await:

    // before
    acl.isAllowed("joed", "blogs", "view", (err, allowed) => { ... });
    // after
    const allowed = await acl.isAllowed("joed", "blogs", "view");
  3. Constructor uses imported backends (no more new acl.redisBackend(...)):

    import { Acl, RedisBackend } from "acl-next";
    const acl = new Acl(new RedisBackend(redisClient));
  4. MongoDB options are an object: new MongoDBBackend(db, { prefix, useSingle }) instead of positional args.

  5. Upgrade drivers to redis@4+ / mongodb@4+.

  6. Numeric IDs come back as strings (e.g. roleUsers returns ["3"], not [3]).

Development

npm run build       # ESM + CJS + .d.ts via tsup
npm run typecheck   # tsc --noEmit
npm run lint        # biome
npm test            # vitest (Redis/Mongo suites use testcontainers → Docker required)

The Redis/MongoDB suites spin up real databases with testcontainers (needs Docker). The Memory and unit suites run without Docker.

License

MIT. Original work © 2011-2013 Manuel Astudillo; modernization © 2026 Nordin Zahari.