https://github.com/seeden/granter
Composable, type-safe authorization for TypeScript. ⭐️ Star to support my work
https://github.com/seeden/granter
abac access-control async authorization authz better-auth express permissions rbac security typescript
Last synced: 8 months ago
JSON representation
Composable, type-safe authorization for TypeScript. ⭐️ Star to support my work
- Host: GitHub
- URL: https://github.com/seeden/granter
- Owner: seeden
- License: mit
- Created: 2025-10-06T18:40:56.000Z (9 months ago)
- Default Branch: main
- Last Pushed: 2025-10-07T06:30:28.000Z (9 months ago)
- Last Synced: 2025-10-08T09:58:39.232Z (9 months ago)
- Topics: abac, access-control, async, authorization, authz, better-auth, express, permissions, rbac, security, typescript
- Language: TypeScript
- Homepage:
- Size: 26.4 KB
- Stars: 2
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# granter
> Composable, type-safe authorization for TypeScript
[](https://www.npmjs.com/package/granter)
[](https://opensource.org/licenses/MIT)
**📚 [Read the full documentation →](https://seeden.github.io/granter)**
## Why granter?
✨ **Composable** - Build complex permissions from simple rules
🔒 **Type-safe** - Full TypeScript inference with generic contexts
⚡ **Async-first** - Works seamlessly with databases, APIs, and DataLoader
🔧 **Framework-agnostic** - Works with Express, Hono, Next.js, GraphQL, and more
🪶 **Zero dependencies** - Lightweight and performant
## Quick Example
```typescript
import { permission, or } from 'granter';
// Define permissions
const isAdmin = permission('isAdmin', (ctx) => ctx.user.role === 'admin');
const isPostOwner = permission('isPostOwner', (ctx, post) => post.authorId === ctx.user.id);
// Compose permissions
const canEditPost = or(isPostOwner, isAdmin);
// Use them - permissions are callable!
if (await canEditPost(ctx, post)) {
await updatePost(post);
}
// Require permission (throws if denied)
await canEditPost.orThrow(ctx, post);
// Filter arrays
const editablePosts = await canEditPost.filter(ctx, allPosts);
// Debug permission checks
const explanation = await canEditPost.explain(ctx, post);
```
## Installation
```bash
npm install granter
```
## Documentation
Visit **[seeden.github.io/granter](https://seeden.github.io/granter)** for the complete documentation:
- **[Getting Started](https://seeden.github.io/granter/docs/getting-started)** - Install and use granter in 5 minutes
- **[Core Concepts](https://seeden.github.io/granter/docs/permissions)** - Learn about permissions, operators, and methods
- **[Express Example](https://seeden.github.io/granter/docs/express)** - Complete REST API example
- **[API Reference](https://seeden.github.io/granter/docs/api-reference)** - Full API documentation
## Key Features
### Composable Operators
```typescript
import { and, or, not } from 'granter';
// Combine with OR (any must pass)
const canEdit = or(isPostOwner, isAdmin, isModerator);
// Combine with AND (all must pass)
const canPublish = and(isAuthenticated, isVerified, isPostOwner);
// Negate permissions
const canComment = and(isAuthenticated, not(isBanned));
```
### Powerful Methods
```typescript
// Check permission (returns boolean)
if (await canEdit(ctx, post)) {
/* ... */
}
// Require permission (throws if denied)
await canEdit.orThrow(ctx, post);
// Filter arrays to allowed items
const editable = await canEdit.filter(ctx, allPosts);
// Debug permission checks
const explanation = await canEdit.explain(ctx, post);
```
### Simplify with `withContext()`
```typescript
import { withContext } from 'granter';
const abilities = withContext(ctx, {
canEditPost,
canDeletePost,
});
// No need to pass ctx anymore!
if (await abilities.canEditPost(post)) {
await updatePost(post);
}
```
## Framework Examples
granter works with any TypeScript project. See the [documentation](https://seeden.github.io/granter) for complete examples with:
- **[Express.js](https://seeden.github.io/granter/docs/express)** - REST API with middleware
- **[Next.js](https://seeden.github.io/granter/docs/nextjs)** - Server Actions and App Router
- **[GraphQL](https://seeden.github.io/granter/docs/graphql)** - Apollo Server with DataLoader
- **[React](https://seeden.github.io/granter/docs/react)** - Context and hooks patterns
## Authentication Integration
granter is **authorization-only** and works with any authentication library:
- [Auth.js / NextAuth.js](https://seeden.github.io/granter/docs/auth-js)
- [Clerk](https://seeden.github.io/granter/docs/clerk)
- [Supabase Auth](https://seeden.github.io/granter/docs/supabase)
- Custom JWT/Sessions
- And more...
See the [Authentication Integration](https://seeden.github.io/granter/docs) guide for complete examples.
## TypeScript Support
granter is built with TypeScript and provides full type inference:
```typescript
type AppContext = {
user: { id: string; role: string };
db: Database;
};
type Post = {
id: string;
authorId: string;
};
const canEdit = or(isPostOwner, isAdmin);
// ✅ Type-safe: ctx and post are fully typed
await canEdit(ctx, post);
// ❌ TypeScript error: missing resource
await canEdit(ctx);
```
## Testing
Permissions are pure functions, making them easy to test:
```typescript
import { describe, it, expect } from 'vitest';
describe('canEditPost', () => {
it('allows post owner', async () => {
const ctx = { user: { id: '1', role: 'user' }, db };
const post = { id: '123', authorId: '1' };
expect(await canEditPost(ctx, post)).toBe(true);
});
it('allows admin', async () => {
const ctx = { user: { id: '2', role: 'admin' }, db };
const post = { id: '123', authorId: '1' };
expect(await canEditPost(ctx, post)).toBe(true);
});
it('denies other users', async () => {
const ctx = { user: { id: '3', role: 'user' }, db };
const post = { id: '123', authorId: '1' };
expect(await canEditPost(ctx, post)).toBe(false);
});
});
```
## Advanced Features
### Parallel Operators
Use `orParallel()` and `andParallel()` for DataLoader batching:
```typescript
import { orParallel, andParallel } from 'granter';
// Run all checks in parallel (no short-circuit)
const canEdit = orParallel(isPostOwner, isAdmin, isModerator);
```
**[Learn more about parallel execution →](https://seeden.github.io/granter/docs/parallel-execution)**
### Debug with `.explain()`
Understand why permissions passed or failed:
```typescript
const explanation = await canEdit.explain(ctx, post);
console.log(JSON.stringify(explanation, null, 2));
// {
// "name": "(isPostOwner OR isAdmin)",
// "value": false,
// "duration": 15.23,
// "children": [
// { "name": "isPostOwner", "value": false, "duration": 8.12 },
// { "name": "isAdmin", "value": false, "duration": 7.11 }
// ]
// }
```
**[Learn more about debugging →](https://seeden.github.io/granter/docs/debugging)**
## Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
## License
MIT © [seeden](https://github.com/seeden)
---
**📚 [View Full Documentation](https://seeden.github.io/granter)** | **[GitHub](https://github.com/seeden/granter)** | **[npm](https://www.npmjs.com/package/granter)**