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

@fjell/express-router

v4.4.82

Published

Express Router for Fjell

Readme

Fjell Express Router

Express Router for Fjell - Automatic REST API generation with hierarchical data management for Express.js applications.

Fjell Express Router provides a powerful abstraction layer for creating Express.js REST APIs that automatically handle CRUD operations for complex, hierarchical data models. Built on the Fjell framework, it enables rapid development of enterprise-grade APIs with built-in support for nested resources, business logic integration, and type-safe operations.

🎉 Version 3.0 - Core Operations Integration

Version 3.0 introduces seamless integration with @fjell/core Operations interface, providing:

  • Unified Interface: Works directly with the standardized Operations interface from @fjell/core
  • Better Type Safety: Enhanced TypeScript support with explicit method types
  • Cross-Package Compatibility: Compatible with all Fjell packages (@fjell/lib, @fjell/cache, @fjell/client-api)
  • Automatic REST API: Generate complete REST endpoints from any Operations implementation
  • Consistent Routing: Same routing behavior across all Operations sources

What Changed in v3.0

The router now works with any Operations implementation from the Fjell ecosystem:

import { createOperationsRouter } from '@fjell/express-router';
import { createLibrary } from '@fjell/lib';

// Works with @fjell/lib Operations
const library = createLibrary(registry, { keyType: 'user' }, userOperations, userOptions);
const router = new PItemRouter(library, 'user');
app.use('/api/users', router.getRouter());

Migration is seamless - existing code continues to work without changes. See MIGRATION_v3.md for details.

Features

  • Automatic CRUD Routes: Generate complete REST endpoints with minimal configuration
  • Hierarchical Data Support: Handle complex nested relationships with parent-child routing
  • Type-Safe Operations: Full TypeScript support with generic type constraints
  • Flexible Business Logic: Easy integration of custom business rules and validations
  • Express.js Integration: Seamless mounting as Express middleware with full compatibility
  • Built-in Error Handling: Comprehensive error responses with proper HTTP status codes
  • Extensible Architecture: Support for custom actions, facets, and middleware

Installation

npm install @fjell/express-router

Or with npm:

npm install @fjell/express-router

Quick Start

import { PItemRouter, createRegistry } from '@fjell/express-router';
import { Item } from '@fjell/core';
import express from 'express';

// Define your data model
interface User extends Item<'user'> {
  id: string;
  name: string;
  email: string;
  role: 'admin' | 'user' | 'guest';
}

// Create Express app and registry
const app = express();
const registry = createRegistry();

// Create instance and router
const userInstance = registry.getInstance(
  'user',
  userOperations, // Your business logic implementation
  userOptions     // Configuration options
);

const userRouter = new PItemRouter(userInstance, 'user');

// Mount router - automatically creates CRUD endpoints
app.use('/api/users', userRouter.getRouter());

// Start server
app.listen(3000, () => {
  console.log('Server running on http://localhost:3000');
});

This automatically creates the following endpoints:

  • GET /api/users - List all users
  • GET /api/users/:userPk - Get specific user
  • POST /api/users - Create new user
  • PUT /api/users/:userPk - Update user
  • DELETE /api/users/:userPk - Delete user

Core Concepts

Router Types

PItemRouter - For primary entities that exist independently:

const userRouter = new PItemRouter(userInstance, 'user');
const productRouter = new PItemRouter(productInstance, 'product');

CItemRouter - For child entities that belong to parent entities:

const orderRouter = new PItemRouter(orderInstance, 'order');
const orderItemRouter = new CItemRouter(orderItemInstance, 'orderItem', orderRouter);

Hierarchical Routing

Create nested routes for complex relationships:

// Organization -> Department -> Employee hierarchy
const orgRouter = new PItemRouter(orgInstance, 'organization');
const deptRouter = new CItemRouter(deptInstance, 'department', orgRouter);
const empRouter = new CItemRouter(empInstance, 'employee', deptRouter);

// Mount hierarchical routes
app.use('/api/organizations', orgRouter.getRouter());
app.use('/api/organizations/:organizationPk/departments', deptRouter.getRouter());
app.use('/api/organizations/:organizationPk/departments/:departmentPk/employees', empRouter.getRouter());

This creates endpoints like:

  • GET /api/organizations/org-1/departments/dept-1/employees
  • POST /api/organizations/org-1/departments/dept-1/employees
  • GET /api/organizations/org-1/departments/dept-1/employees/emp-1

Advanced Usage

Router-Level Handlers

Fjell Express Router supports router-level handlers for actions, facets, allActions, and allFacets. These handlers take precedence over library-level handlers and are ideal for implementing cross-system integrations, external service calls, or complex business logic that doesn't belong in the library layer.

When to Use Router-Level Handlers

  • Cross-system integrations: When you need to call external services (APIs, databases, etc.)
  • Complex aggregations: When you need to combine data from multiple sources
  • External notifications: When you need to send emails, push notifications, etc.
  • Audit logging: When you need to log actions to external systems
  • Caching strategies: When you need custom caching logic

Example: Router-Level Handlers

import { PItemRouter, CItemRouter } from '@fjell/express-router';
import { Item, PriKey, ComKey } from '@fjell/core';
import { Request, Response } from 'express';

// Define your data models
interface User extends Item<'user'> {
  id: string;
  name: string;
  email: string;
}

interface Post extends Item<'post', 'user'> {
  id: string;
  title: string;
  content: string;
  authorId: string;
}

// Create routers with router-level handlers
const userRouter = new PItemRouter(userInstance, 'user', {
  // Router-level action handlers
  actions: {
    activate: async (req: Request, res: Response, ik: PriKey<'user'>) => {
      // Custom logic: send activation email, update status, etc.
      const user = await userInstance.operations.get(ik);
      await emailService.sendActivationEmail(user.email);
      await auditService.logAction('user_activated', ik.pk);

      res.json({
        message: 'User activated via router handler',
        userId: ik.pk,
        emailSent: true
      });
    },
    deactivate: async (req: Request, res: Response, ik: PriKey<'user'>) => {
      // Custom logic: send deactivation notification, update status, etc.
      const user = await userInstance.operations.get(ik);
      await notificationService.sendDeactivationNotification(user);
      await auditService.logAction('user_deactivated', ik.pk);

      res.json({
        message: 'User deactivated via router handler',
        userId: ik.pk,
        notificationSent: true
      });
    }
  },

  // Router-level facet handlers
  facets: {
    profile: async (req: Request, res: Response, ik: PriKey<'user'>) => {
      // Custom logic: aggregate data from multiple sources
      const user = await userInstance.operations.get(ik);
      const socialData = await socialService.getUserData(ik.pk);
      const preferences = await preferenceService.getUserPreferences(ik.pk);

      res.json({
        userId: ik.pk,
        basicInfo: user,
        socialInfo: socialData,
        preferences: preferences
      });
    },
    stats: async (req: Request, res: Response, ik: PriKey<'user'>) => {
      // Custom logic: calculate statistics from multiple data sources
      const postsCount = await postService.getUserPostsCount(ik.pk);
      const commentsCount = await commentService.getUserCommentsCount(ik.pk);
      const likesReceived = await likeService.getUserLikesCount(ik.pk);

      res.json({
        userId: ik.pk,
        postsCount,
        commentsCount,
        likesReceived,
        lastActivity: new Date()
      });
    }
  },

  // Router-level all action handlers
  allActions: {
    bulkActivate: async (req: Request, res: Response) => {
      // Custom logic: batch processing, external service integration
      const { userIds } = req.body;
      const results = await Promise.all(
        userIds.map(async (userId) => {
          const user = await userInstance.operations.get({ kt: 'user', pk: userId });
          await emailService.sendActivationEmail(user.email);
          return { userId, emailSent: true };
        })
      );

      res.json({
        message: 'Bulk activation via router handler',
        processedUsers: results.length,
        results
      });
    }
  },

  // Router-level all facet handlers
  allFacets: {
    userStats: async (req: Request, res: Response) => {
      // Custom logic: aggregate statistics from multiple systems
      const totalUsers = await userInstance.operations.all().then(users => users.length);
      const activeUsers = await analyticsService.getActiveUsersCount();
      const newUsersThisMonth = await analyticsService.getNewUsersThisMonth();

      res.json({
        totalUsers,
        activeUsers,
        newUsersThisMonth,
        systemHealth: 'excellent'
      });
    }
  }
});

// Child router with router-level handlers
const postRouter = new CItemRouter(postInstance, 'post', userRouter, {
  actions: {
    publish: async (req: Request, res: Response, ik: ComKey<'post', 'user'>) => {
      // Custom logic: publish to social media, send notifications
      const post = await postInstance.operations.get(ik);
      await socialMediaService.publishToTwitter(post);
      await socialMediaService.publishToLinkedIn(post);
      await notificationService.notifyFollowers(post.authorId, post);

      res.json({
        message: 'Post published via router handler',
        postId: ik.pk,
        socialMediaPosted: true,
        notificationsSent: true
      });
    }
  },

  facets: {
    analytics: async (req: Request, res: Response, ik: ComKey<'post', 'user'>) => {
      // Custom logic: aggregate analytics from multiple sources
      const views = await analyticsService.getPostViews(ik.pk);
      const likes = await socialService.getPostLikes(ik.pk);
      const shares = await socialService.getPostShares(ik.pk);

      res.json({
        postId: ik.pk,
        views,
        likes,
        shares,
        engagementRate: (likes + shares) / views
      });
    }
  }
});

Handler Priority

Router-level handlers take precedence over library-level handlers:

  1. Router-level handler exists: Use router handler
  2. Router-level handler doesn't exist: Fallback to library handler
  3. No handlers exist: Return error

This allows you to:

  • Override specific handlers while keeping others at the library level
  • Implement handlers only at the router level when needed
  • Mix and match router and library handlers as appropriate

Handler Signatures

Item Actions & Facets (for PItemRouter):

async (req: Request, res: Response, ik: PriKey<S>) => Promise<void>

Item Actions & Facets (for CItemRouter):

async (req: Request, res: Response, ik: ComKey<S, L1, L2, L3, L4, L5>) => Promise<void>

All Actions & Facets (for both router types):

async (req: Request, res: Response) => Promise<void>

Custom Business Logic

Add custom routes alongside automatic CRUD operations:

const router = userRouter.getRouter();

// Add custom business logic routes
router.get('/analytics', async (req, res) => {
  const analytics = await userInstance.operations.getAnalytics();
  res.json(analytics);
});

router.post('/:userPk/activate', async (req, res) => {
  const userPk = req.params.userPk;
  const user = await userInstance.operations.activate(userPk);
  res.json(user);
});

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

Middleware Integration

Add Express middleware for authentication, validation, and more:

import { authenticate, authorize, validateRequest } from './middleware';

// Global middleware
app.use(express.json());
app.use(authenticate);

// Router-specific middleware
const protectedRouter = userRouter.getRouter();
protectedRouter.use(authorize(['admin', 'user']));
protectedRouter.use('/sensitive-endpoint', validateRequest);

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

Error Handling

Built-in error handling with proper HTTP status codes:

// Automatic error responses for common scenarios:
// 404 - Entity not found
// 400 - Invalid request data
// 500 - Internal server errors

// Custom error handling
app.use((err, req, res, next) => {
  console.error('API Error:', err);
  res.status(err.status || 500).json({
    error: err.message || 'Internal Server Error',
    ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
  });
});

Data Model Requirements

Your data models must extend the Fjell Item interface:

import { Item, UUID } from '@fjell/core';

interface User extends Item<'user'> {
  id: string;
  name: string;
  email: string;
  // ... other properties
}

interface Order extends Item<'order'> {
  id: string;
  customerId: string;
  total: number;
  // ... other properties
}

interface OrderItem extends Item<'orderItem', 'order'> {
  id: string;
  productId: string;
  quantity: number;
  price: number;
  // ... other properties
}

Configuration

Configure your instances with business operations and options:

const userInstance = registry.getInstance(
  'user',
  {
    // Business operations implementation
    create: async (item) => { /* create logic */ },
    get: async (pk) => { /* get logic */ },
    update: async (pk, updates) => { /* update logic */ },
    delete: async (pk) => { /* delete logic */ },
    list: async (query) => { /* list logic */ },
    // ... other operations
  },
  {
    // Configuration options
    allowedMethods: ['GET', 'POST', 'PUT', 'DELETE'],
    validation: { /* validation rules */ },
    facets: { /* custom facets */ },
    actions: { /* custom actions */ },
    // ... other options
  }
);

Examples

This package includes comprehensive examples in the examples/ directory:

  • basic-router-example.ts - Start here for fundamental usage patterns
  • nested-router-example.ts - Hierarchical data management with organizations/departments/employees
  • router-handlers-example.ts - Router-level handlers for cross-system integrations
  • full-application-example.ts - Production-ready e-commerce application

Run examples:

# Basic example
npx tsx examples/basic-router-example.ts

# Nested routing example
npx tsx examples/nested-router-example.ts

# Router handlers example
npx tsx examples/router-handlers-example.ts

# Full application example
npx tsx examples/full-application-example.ts

API Reference

PItemRouter<T, S>

Primary item router for top-level entities.

Constructor

new PItemRouter<T, S>(instance: Instance<T, S>, keyType: S, options?: ItemRouterOptions)

Methods

  • getRouter(): Router - Get Express router instance
  • getPkType(): S - Get primary key type
  • createItem(req, res) - Handle POST requests
  • getItem(req, res) - Handle GET requests for single items
  • updateItem(req, res) - Handle PUT requests
  • deleteItem(req, res) - Handle DELETE requests

CItemRouter<T, S, L1, ...>

Child item router for nested entities.

Constructor

new CItemRouter<T, S, L1>(
  instance: Instance<T, S, L1>,
  keyType: S,
  parentRouter: ItemRouter<L1>,
  options?: ItemRouterOptions
)

Inherits all methods from ItemRouter with additional parent-child relationship handling.

createRegistry()

Factory function to create a new registry instance for managing multiple routers.

const registry = createRegistry();
const instance = registry.getInstance(keyType, operations, options);

Requirements

  • Node.js >= 21
  • TypeScript >= 5.0
  • Express.js >= 5.0

Dependencies

  • @fjell/core - Core Fjell framework types and utilities
  • @fjell/lib - Fjell library components
  • @fjell/logging - Structured logging for Fjell applications
  • @fjell/registry - Registry management for Fjell instances
  • express - Express.js web framework
  • deepmerge - Deep object merging utility

Contributing

Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes with tests
  4. Ensure all tests pass
  5. Submit a pull request

License

Licensed under the Apache License 2.0. See the LICENSE file for details.

Support

Built with care by the Fjell team.