Backend
The Backend is the administrative interface for the Heimdall platform, built with Next.js 16 and the App Router.
Features
Dashboard
The main dashboard provides an overview of platform statistics and recent activity:
- Active connections count
- GPS data points
- User statistics
- Recent activity feed
- Real-time updates via WebSocket
GPS Management
View and manage GPS tracking data:
- Real-time GPS position display
- Historical GPS data table with pagination
- GPS data export functionality
- Interactive map visualization
User Management
Manage platform users (Admin only):
- View all registered users
- View and edit user profiles
- Assign roles and permissions
- View user activity and login history
- Suspend/ban users
- Force logout sessions
Role Management
Create and manage roles with fine-grained permissions:
- Create custom roles
- Assign permissions to roles
- Set role-based 2FA requirements
- View role assignments
API Key Management
Manage API keys for system access:
- Create new API keys
- Set key permissions and scopes
- View key usage statistics
- Revoke API keys
Developer Tools
Tools for developers integrating with Heimdall:
- OAuth application management
- Create/edit OAuth apps
- View client credentials
- Manage redirect URIs
- Test OAuth flows
Pages & Routes
| Route | Description | Auth Required |
|---|---|---|
/ | Landing page | No |
/auth/login | Login page | No |
/auth/error | Auth error page | No |
/dashboard | Main dashboard | Yes |
/users | User management | Yes (Admin) |
/users/[id] | User details | Yes (Admin) |
/roles | Role management | Yes (Admin) |
/roles/new | Create role | Yes (Admin) |
/roles/[id] | Edit role | Yes (Admin) |
/gps | GPS data list | Yes |
/gps/[id] | GPS details | Yes |
/settings | User settings | Yes |
/developer/oauth-apps | OAuth app list | Yes |
/developer/oauth-apps/new | Create OAuth app | Yes |
/developer/oauth-apps/[id] | Edit OAuth app | Yes |
/developer/api-keys | API key management | Yes |
Authentication
The Backend Dashboard uses NextAuth with a custom Heimdall provider:
// src/lib/auth/heimdall-provider.ts
import { OAuthConfig } from "next-auth/providers";
export const HeimdallProvider: OAuthConfig<Profile> = {
id: "heimdall",
name: "Heimdall",
type: "oauth",
authorization: {
url: `${process.env.HEIMDALL_ID_URL}/oauth/authorize`,
params: { scope: "openid profile email" }
},
token: `${process.env.API_URL}/v1/oauth/token`,
userinfo: `${process.env.API_URL}/v1/oauth/userinfo`,
profile(profile) {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: profile.picture
};
}
};
Session Handling
Sessions are managed via NextAuth with JWT strategy. Next.js 16 renamed middleware.ts to proxy.ts:
// proxy.ts (Next.js 16+)
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
import { getToken } from "next-auth/jwt";
export async function proxy(request: NextRequest) {
const { pathname } = request.nextUrl;
// Get the token (session) - use app-specific cookie name
const token = await getToken({
req: request,
secret: process.env.NEXTAUTH_SECRET,
cookieName: process.env.NODE_ENV === "production"
? "__Secure-backend.session-token"
: "backend.session-token",
});
// Require authentication for protected routes
if (!token && pathname.startsWith("/dashboard")) {
const loginUrl = new URL("/login", request.url);
loginUrl.searchParams.set("callbackUrl", pathname);
return NextResponse.redirect(loginUrl);
}
return NextResponse.next();
}
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};
Components
Protected Route
Wrap pages that require authentication:
import { ProtectedRoute } from "@/components/auth/ProtectedRoute";
export default function DashboardPage() {
return (
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
);
}
Protected Content
Conditionally render content based on authentication:
import { ProtectedContent } from "@/components/auth/ProtectedContent";
export default function Page() {
return (
<ProtectedContent
fallback={<LoginPrompt />}
requiredPermissions={["gps:read"]}
>
<GpsDataTable />
</ProtectedContent>
);
}
User Button
Display user info and logout:
import { UserButton } from "@/components/auth/UserButton";
export default function Header() {
return (
<nav>
<UserButton />
</nav>
);
}
Hooks
useAuth
Access authentication state:
import { useAuth } from "@/hooks/useAuth";
function Component() {
const { user, isLoading, isAuthenticated } = useAuth();
if (isLoading) return <Spinner />;
if (!isAuthenticated) return <LoginPrompt />;
return <div>Welcome, {user.name}</div>;
}
usePermissions
Check user permissions:
import { usePermissions } from "@/hooks/usePermissions";
function Component() {
const { permissions, hasPermission, isLoading } = usePermissions();
if (hasPermission("gps:write")) {
return <CreateGpsButton />;
}
return null;
}
API Integration
GraphQL Client
The dashboard uses Apollo Client for GraphQL:
// src/components/providers/ApolloWrapper.tsx
"use client";
import { ApolloProvider, ApolloClient, InMemoryCache } from "@apollo/client";
import { useSession } from "next-auth/react";
export function ApolloWrapper({ children }) {
const { data: session } = useSession();
const client = new ApolloClient({
uri: `${process.env.NEXT_PUBLIC_API_URL}/v1/gql`,
cache: new InMemoryCache(),
headers: {
Authorization: session?.accessToken
? `Bearer ${session.accessToken}`
: ""
}
});
return <ApolloProvider client={client}>{children}</ApolloProvider>;
}
Example Query
import { gql, useQuery } from "@apollo/client";
const GET_GPS_DATA = gql`
query GetGpsData($page: Int!, $limit: Int!) {
gpsList(page: $page, limit: $limit) {
data {
id
latitude
longitude
altitude
timestamp
speed
}
pagination {
page
limit
total
totalPages
}
}
}
`;
function GpsDataTable() {
const { data, loading, error } = useQuery(GET_GPS_DATA, {
variables: { page: 1, limit: 10 }
});
if (loading) return <Spinner />;
if (error) return <Error message={error.message} />;
return (
<table>
{data.gpsList.data.map((gps) => (
<tr key={gps.id}>
<td>{gps.latitude}</td>
<td>{gps.longitude}</td>
<td>{gps.speed}</td>
</tr>
))}
</table>
);
}
Layouts
Dashboard Layout
The main layout for authenticated pages:
// src/app/dashboard/layout.tsx
import { DashboardLayout } from "@/components/layouts/DashboardLayout";
export default function Layout({ children }) {
return <DashboardLayout>{children}</DashboardLayout>;
}
Features:
- Sidebar navigation
- Header with user menu
- Breadcrumbs
- Footer
Overlay Layout
Full-screen overlay layout for modals:
import { OverlayLayout } from "@/layouts/OverlayLayout";
export default function ModalPage() {
return (
<OverlayLayout>
<Modal />
</OverlayLayout>
);
}
UI Components
The dashboard includes a set of reusable UI components:
Button
import { Button } from "@/components/ui/Button";
<Button variant="primary" size="lg" onClick={handleClick}>
Click Me
</Button>
<Button variant="outline" disabled>
Disabled
</Button>
Alert
import { Alert } from "@/components/ui/Alert";
<Alert type="success" title="Success!">
Your changes have been saved.
</Alert>
<Alert type="error" title="Error">
Something went wrong.
</Alert>
Modal
import { Modal } from "@/components/ui/Modal";
<Modal isOpen={isOpen} onClose={handleClose} title="Confirm Action">
<p>Are you sure you want to proceed?</p>
<div>
<Button onClick={handleClose}>Cancel</Button>
<Button variant="primary" onClick={handleConfirm}>
Confirm
</Button>
</div>
</Modal>
Loading Spinner
import { LoadingSpinner } from "@/components/ui/LoadingSpinner";
<LoadingSpinner size="lg" />
Floating Input
import { FloatingInput } from "@/components/ui/FloatingInput";
<FloatingInput
label="Email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
Environment Variables
| Variable | Description | Required |
|---|---|---|
NEXTAUTH_URL | The base URL of the app | Yes |
NEXTAUTH_SECRET | Secret for signing tokens | Yes |
NEXT_PUBLIC_API_URL | Public API URL | Yes |
API_URL | Server-side API URL | Yes |
HEIMDALL_ID_URL | Heimdall ID service URL | Yes |
Development
cd platform/backend
# Install dependencies
pnpm install
# Run development server
pnpm dev
# Build for production
pnpm build
# Start production server
pnpm start
# Run linting
pnpm lint
# Run E2E tests
pnpm test:e2e
Next Steps
- ID - Identity service documentation
- Components - Shared component documentation
- Authentication Flow - How auth works