BETA

Routing

CORE

Harpy.js uses NestJS' routing system: controller methods map to routes and the framework automatically discovers and registers them during startup.

🎯 Key Concept: Routes are defined using decorators on controller methods. Feature modules register their routes with a central navigation registry provided by the core.

Dynamic Navigation Architecture

Harpy.js provides a small, opinionated navigation registry built in the core package. Instead of requiring a central routing manifest, feature modules register their own documentation routes during the Nest module lifecycle. This makes adding, removing or re-ordering sections simple and local to each feature.

Core concepts:

  • RouterModule — a small global module that provides the runtime NavigationService.
  • NavigationService — registry API used by features to add sections and items.
  • AutoRegisterModule — an abstract helper in core that feature modules can extend to simplify registration on module init.

How it works (high level)

The application imports RouterModulefrom @hepta-solutions/harpy-core (once, usually in the root AppModule). The module registers and exports theNavigationService, which feature modules use to register navigation entries during the NestJS OnModuleInitphase. The app's layout then reads the registry to render sidebars and navigation widgets.

Example: feature module (illustrative)

The snippet below is a minimal, generic example (the "Eagles" feature) that demonstrates the recommended pattern: a feature module extends the core's AutoRegisterModule and delegates navigation registration to a feature service. This is an illustrative example — not the project's production implementation.

1// features/eagles/eagles.module.ts2import { Module } from '@nestjs/common';3import { EaglesController } from './eagles.controller';4import { EaglesService } from './eagles.service';5import {6  NavigationService,7  NavigationRegistry,8  AutoRegisterModule,9} from '@hepta-solutions/harpy-core';1011@Module({12  controllers: [EaglesController],13  providers: [EaglesService],14})15export class EaglesModule extends AutoRegisterModule {16  constructor(17    navigationService: NavigationService,18    private readonly eaglesService: EaglesService,19  ) {20    super(navigationService);21  }2223  protected registerNavigation(navigation: NavigationRegistry): void {24    // Delegate to the feature service; AutoRegisterModule will call this25    this.eaglesService.registerNavigation(navigation);26  }27}

Example: feature service

The service is responsible for adding the actual navigation items (sections and links) to the registry.

1// features/eagles/eagles.service.ts2import { Injectable } from '@nestjs/common';3import { NavigationRegistry } from '@hepta-solutions/harpy-core';45@Injectable()6export class EaglesService {7  /**8   * Register feature documentation in the shared navigation9   * This is called during module initialization (OnModuleInit)10   */11  registerNavigation(navigationService: NavigationRegistry) {12    // Add this feature to the Core Concepts section13    navigationService.addItemToSection('core-concepts', {14      id: 'eagles',15      title: 'Eagles (Feature Example)',16      href: '/docs/eagles',17    });18  }19}

Example: controller (rendering the page)

The controller injects the core NavigationServiceand passes the registry to the view for rendering.

1// features/eagles/eagles.controller.ts2import { Controller, Get } from '@nestjs/common';3import { JsxRender } from '@hepta-solutions/harpy-core';4import EaglesPage from './views/eagles-page';5import DashboardLayout from '../../layouts/dashboard-layout';6import { NavigationService } from '@hepta-solutions/harpy-core';7import { getDictionary } from '../../i18n/get-dictionary';89@Controller('docs')10export class EaglesController {11  constructor(private readonly navigationService: NavigationService) {}1213  @Get('eagles')14  @JsxRender(EaglesPage, {15    layout: DashboardLayout,16    meta: {17      title: 'Eagles Guide - Harpy.js Framework (EXAMPLE)',18      description:19        'Example documentation controller for the Eagles feature. Demonstrates how to render a JSX page and provide navigation sections.',20    },21  })22  async eagles() {23    const sections = this.navigationService.getAllSections();24    const dict = await getDictionary('en');2526    return {27      sections,28      dict,29      locale: 'en',30    };31  }32}

Example: view (use core Link component)

In views prefer the framework <Link />component from @hepta-solutions/harpy-core to avoid client-side navigation and hydration mismatches.

1// features/eagles/views/eagles-page.tsx2import { Link } from '@hepta-solutions/harpy-core';34export default function EaglesPage({ sections }) {5  return (6    <div className="grid grid-cols-4 gap-6">7      <aside className="col-span-1">8        {sections.map(s => (9          <div key={s.id} className="mb-4">10            <h4 className="font-semibold">{s.title}</h4>11            <ul className="mt-2 space-y-1">12              {s.items.map(i => (13                <li key={i.id}>14                  <Link href={i.href} className="text-amber-600">{i.title}</Link>15                </li>16              ))}17            </ul>18          </div>19        ))}20      </aside>21      <main className="col-span-3">{/* Feature content */}</main>22    </div>23  );24}

Migration notes

If you are migrating from an older app that previously re-exported navigation providers from local shared modules, importRouterModulefrom @hepta-solutions/harpy-core in your root module, remove legacy re-exports, and update feature modules to extend AutoRegisterModule(or implement OnModuleInit) and call your service's registerNavigation()method.

This approach avoids duplicate provider/DI issues and centralizes navigation responsibilities in the core package.

Each controller defines its route and passes the complete navigation structure to the layout for rendering the sidebar.

Controlling Section Order

By default the core navigation registry preserves insertion order for registered sections. You have three practical options to ensure a specific section (for example Core Concepts) appears first:

  • Register early: create a small module that registers the section during app startup (import it before other feature modules). This guarantees insertion order places it first.
  • Provide an explicit order: the core types accept an optional numeric orderon sections; lower numbers are shown earlier.
  • Reorder at runtime: use the core helpermoveSectionToFront()when you need to adjust ordering after modules register.

Examples below show each approach.

A — Register early

Create a tiny module that registers the section duringOnModuleInitand import it in the application root before other features.

1// apps/.../features/core-navigation/core-navigation.module.ts2import { Module, Injectable, OnModuleInit } from '@nestjs/common';3import { NavigationService } from '@hepta-solutions/harpy-core';45@Injectable()6class CoreNavigationRegistrar implements OnModuleInit {7  constructor(private readonly navigationService: NavigationService) {}89  onModuleInit() {10    this.navigationService.registerSection({11      id: 'core-concepts',12      title: 'Core Concepts',13      items: [],14    });15  }16}1718@Module({ providers: [CoreNavigationRegistrar] })19export class CoreNavigationModule {}

B — Use explicit order

When registering a section you can pass an optional numericorder.

1// packages/harpy-core/src/core/types/nav.types.ts2export interface NavSection {3  id: string;4  title: string;5  items: NavItem[];6  order?: number; // lower numbers appear earlier7}
1// registering with an explicit order2navigationService.registerSection({3  id: 'core-concepts',4  title: 'Core Concepts',5  items: [],6  order: 0, // appears before sections with higher order or no order7});

C — Reorder at runtime

If you need to adjust order after modules have registered, callmoveSectionToFront()from any provider to move a section to the first position.

1// packages/harpy-core/src/core/navigation.service.ts2// move an existing section to the front3navigationService.moveSectionToFront('core-concepts');
1// features/eagles/eagles.controller.ts2import { Controller, Get } from '@nestjs/common';3import { JsxRender } from '@hepta-solutions/harpy-core';4import EaglesPage from './views/eagles-page';5import DashboardLayout from '../../layouts/dashboard-layout';6import { NavigationService } from '@hepta-solutions/harpy-core';7import { getDictionary } from '../../i18n/get-dictionary';89@Controller('docs')10export class EaglesController {11  constructor(private readonly navigationService: NavigationService) {}1213  @Get('eagles')14  @JsxRender(EaglesPage, {15    layout: DashboardLayout,16    meta: {17      title: 'Eagles Guide - Harpy.js Framework (EXAMPLE)',18      description:19        'Example documentation controller for the Eagles feature. Demonstrates how to render a JSX page and provide navigation sections.',20      canonical: 'https://www.harpyjs.org/docs/eagles',21      openGraph: {22        title: 'Eagles Guide - Full-Stack NestJS + React SSR',23        description:24          'Example guide for the Eagles feature. Shows patterns for module registration, controllers and views.',25        type: 'website',26        url: 'https://www.harpyjs.org/docs/eagles',27      },28      twitter: {29        card: 'summary_large_image',30        title: 'Eagles Guide',31        description:32          'Example guide to using Harpy.js routing patterns with an Eagles feature.',33      },34    },35  })36  async eagles() {37    const sections = this.navigationService.getAllSections();38    const dict = await getDictionary('en');3940    return {41      sections,42      dict,43      locale: 'en',44    };45  }46}

✅ Benefits of This Architecture

  • No Code Duplication: Navigation structure is defined once and shared
  • Scalable: Add new modules without touching existing code
  • Automatic Discovery: Routes are registered during NestJS module initialization
  • Type-Safe: TypeScript interfaces ensure consistency
  • Modular: Each feature owns its routing configuration

💡Adding a New Module (recommended flow)

Follow this end-to-end flow to add a feature module that registers navigation and plays nicely with the framework core.

  1. Create a new feature module that extends the core abstract base (e.g.BaseFeatureModule).
  2. Implement OnModuleInit and call super(navigationService) in your constructor so the core's navigation service is wired.
  3. Register your section using the helper provided by the base class (e.g.this.registerSection()), and let your feature service add items via the core NavigationService.
  4. Create a controller with a route decorator (e.g.@Get('...')) and read sections from the injected NavigationService.
  5. Import your feature module into the application's root module and ensure the framework RouterModule from @hepta-solutions/harpy-core is imported once in the root so providers are available.

Pro tip: In JSX views prefer the core <Link /> component from @hepta-solutions/harpy-core instead of raw <a> tags to avoid client-side navigation/hydration mismatches.

That's it! When your module initializes it will register routes with the central navigation system and those routes will appear automatically in shared layouts.

✅ For more details: Check out the official NestJS routing documentation here .