🔐 Modular Apps
For basics, see Type-Safe Outpost Names.
INFO
The patterns below are not prescriptive solutions — they demonstrate that vue-router-citadel is flexible enough to integrate with modular architectures and DI containers. Adapt them to your project's conventions.
📦 Modular Architecture
For large applications with modular structure (modular monolith, feature-sliced design), each module can extend the registries in its own declaration file.
Project structure:
src/
├── core/
│ └── citadel/
│ ├── index.ts
│ └── outposts.d.ts # core outposts
├── modules/
│ ├── auth/
│ │ ├── outposts/
│ │ │ ├── index.ts # handlers
│ │ │ └── outposts.d.ts # auth registry
│ │ └── routes.ts
│ ├── admin/
│ │ └── outposts/
│ │ └── outposts.d.ts # admin registry
│ └── billing/
│ └── outposts/
│ └── outposts.d.ts # billing registry
└── main.tsCore module — src/core/citadel/outposts.d.ts:
declare module 'vue-router-citadel' {
interface GlobalOutpostRegistry {
'app:maintenance': true;
'app:feature-flags': true;
}
}Auth module — src/modules/auth/outposts/outposts.d.ts:
declare module 'vue-router-citadel' {
interface GlobalOutpostRegistry {
'auth:check': true;
'auth:refresh-token': true;
}
interface RouteOutpostRegistry {
'auth:require-login': true;
'auth:require-verified': true;
'auth:guest-only': true;
}
}Auth module — src/modules/auth/outposts/index.ts:
import type { NavigationOutpostHandler } from 'vue-router-citadel';
import { useAuthStore } from '../store';
export const authCheckHandler: NavigationOutpostHandler = ({ verdicts }) => {
const auth = useAuthStore();
auth.checkSession();
return verdicts.ALLOW;
};
export const requireLoginHandler: NavigationOutpostHandler = ({ verdicts, to }) => {
const auth = useAuthStore();
if (!auth.isAuthenticated) {
return { name: 'login', query: { redirect: to.fullPath } };
}
return verdicts.ALLOW;
};
export const guestOnlyHandler: NavigationOutpostHandler = ({ verdicts }) => {
const auth = useAuthStore();
if (auth.isAuthenticated) {
return { name: 'dashboard' };
}
return verdicts.ALLOW;
};Auth module — src/modules/auth/index.ts:
import { NavigationOutpostScopes } from 'vue-router-citadel';
import { citadel } from '@/core/citadel';
import { authCheckHandler, requireLoginHandler, guestOnlyHandler } from './outposts';
export function registerAuthModule() {
citadel.deployOutpost([
{
scope: NavigationOutpostScopes.GLOBAL,
name: 'auth:check', // ✓ typed
priority: 5,
handler: authCheckHandler,
},
{
scope: NavigationOutpostScopes.ROUTE,
name: 'auth:require-login', // ✓ typed
handler: requireLoginHandler,
},
{
scope: NavigationOutpostScopes.ROUTE,
name: 'auth:guest-only', // ✓ typed
handler: guestOnlyHandler,
},
]);
}Auth module — src/modules/auth/routes.ts:
export const authRoutes = [
{
path: '/login',
name: 'login',
component: () => import('./pages/Login.vue'),
meta: { outposts: ['auth:guest-only'] }, // ✓ typed
},
{
path: '/dashboard',
name: 'dashboard',
component: () => import('./pages/Dashboard.vue'),
meta: { outposts: ['auth:require-login'] }, // ✓ typed
},
];Admin module — src/modules/admin/outposts/outposts.d.ts:
declare module 'vue-router-citadel' {
interface RouteOutpostRegistry {
'admin:require-role': true;
'admin:audit-log': true;
}
}Main entry — src/main.ts:
import { registerAuthModule } from './modules/auth';
import { registerAdminModule } from './modules/admin';
import { registerBillingModule } from './modules/billing';
registerAuthModule();
registerAdminModule();
registerBillingModule();💉 Dependency Injection
For applications using DI containers (InversifyJS, tsyringe), outposts can be organized as injectable services.
DI tokens — src/di/tokens.ts:
export const TOKENS = {
Citadel: Symbol('Citadel'),
Router: Symbol('Router'),
AuthService: Symbol('AuthService'),
} as const;Citadel service — src/core/citadel/citadel.service.ts:
import { injectable, inject } from 'inversify';
import { createNavigationCitadel, type NavigationCitadelAPI } from 'vue-router-citadel';
import type { Router } from 'vue-router';
import { TOKENS } from '@/di/tokens';
@injectable()
export class CitadelService {
private citadel: NavigationCitadelAPI;
constructor(@inject(TOKENS.Router) router: Router) {
this.citadel = createNavigationCitadel(router);
}
get api() {
return this.citadel;
}
}Auth outposts — src/modules/auth/outposts/auth.outposts.ts:
import { injectable, inject } from 'inversify';
import { NavigationOutpostScopes, type NavigationOutpost } from 'vue-router-citadel';
import { TOKENS } from '@/di/tokens';
import type { AuthService } from '../services/auth.service';
@injectable()
export class AuthOutposts {
constructor(@inject(TOKENS.AuthService) private authService: AuthService) {}
getOutposts(): NavigationOutpost[] {
return [
{
scope: NavigationOutpostScopes.GLOBAL,
name: 'auth:check', // ✓ typed
priority: 5,
handler: ({ verdicts }) => {
this.authService.checkSession();
return verdicts.ALLOW;
},
},
{
scope: NavigationOutpostScopes.ROUTE,
name: 'auth:require-login', // ✓ typed
handler: ({ verdicts, to }) => {
if (!this.authService.isAuthenticated) {
return { name: 'login', query: { redirect: to.fullPath } };
}
return verdicts.ALLOW;
},
},
];
}
}Auth module — src/modules/auth/auth.module.ts:
import { injectable, inject } from 'inversify';
import { TOKENS } from '@/di/tokens';
import type { CitadelService } from '@/core/citadel/citadel.service';
import { AuthOutposts } from './outposts/auth.outposts';
@injectable()
export class AuthModule {
constructor(
@inject(TOKENS.Citadel) private citadel: CitadelService,
@inject(AuthOutposts) private outposts: AuthOutposts,
) {}
register() {
this.citadel.api.deployOutpost(this.outposts.getOutposts());
}
}