Routes and Pages Guide
This guide explains how to create and configure routes and pages in the Open Mercato framework, including authentication, authorization, and metadata configuration.
Table of Contents
- Overview
- Page Routes
- API Routes
- Authentication & Authorization
- Metadata Configuration
- Best Practices
- Examples
Overview
The Open Mercato framework uses a module-based architecture where routes and pages are automatically discovered and registered. Routes are organized by module and can be either:
- Page Routes: Frontend pages that render UI components
- Backend Routes: Admin/backend pages with authentication
- API Routes: RESTful API endpoints for data operations
Page Routes
Frontend Pages
Frontend pages are located in src/modules/<module>/frontend/<path>.tsx and are automatically mapped to /<path>.
// src/modules/example/frontend/products/page.tsx
export default function ProductsPage() {
return (
<div>
<h1>Products</h1>
<p>Browse our product catalog</p>
</div>
)
}
Backend Pages
Backend pages are located in src/modules/<module>/backend/<path>.tsx and are automatically mapped to /backend/<path>.
// src/modules/example/backend/todos/page.tsx
import { Page, PageHeader, PageBody } from '@open-mercato/ui/backend/Page'
export default function TodosPage() {
return (
<Page>
<PageHeader title="Todos" description="Manage your tasks" />
<PageBody>
<TodosTable />
</PageBody>
</Page>
)
}
Page Metadata
Pages can have metadata for navigation, authentication, and other configuration:
// src/modules/example/backend/todos/page.meta.ts
export const metadata = {
requireAuth: true,
requireRoles: ['admin'] as const,
requireFeatures: ['example.todos.view'],
pageTitle: 'Todos',
pageGroup: 'Example',
pageOrder: 20,
icon: 'CheckSquare',
navHidden: false,
visible: true,
enabled: true
}
Metadata Properties:
requireAuth: Whether authentication is required (default: false)requireRoles: Array of roles required to access the pagepageTitle: Title displayed in navigation and page headerpageGroup: Group for organizing pages in navigationpageOrder: Order within the group (lower numbers appear first)icon: Icon name for navigation (optional)navHidden: Hide from navigation menu (default: false)visible: Whether the page is visible (default: true)enabled: Whether the page is enabled (default: true)
API Routes
API routes are located in src/modules/<module>/api/<path>/route.ts and are automatically mapped to /api/<path>.
Basic API Route
// src/modules/example/api/todos/route.ts
import { NextResponse } from 'next/server'
import { createRequestContainer } from '@/lib/di/container'
import { getAuthFromCookies } from '@/lib/auth/server'
export async function GET(request: Request) {
try {
const container = await createRequestContainer()
const queryEngine = container.resolve<QueryEngine>('queryEngine')
// Your logic here
const todos = await queryEngine.find('todo', {
// query options
})
return NextResponse.json({ items: todos, total: todos.length })
} catch (error) {
return NextResponse.json({ error: 'Internal Server Error' }, { status: 500 })
}
}
Per-Method Metadata
API routes support per-HTTP-method metadata for fine-grained authorization:
// src/modules/example/api/todos/route.ts
export const metadata = {
GET: {
requireAuth: true,
requireRoles: ['admin', 'user'],
requireFeatures: ['entities.records.view']
},
POST: {
requireAuth: true,
requireRoles: ['admin', 'superuser']
},
PUT: {
requireAuth: true,
requireRoles: ['admin'],
requireFeatures: ['entities.records.manage']
},
DELETE: {
requireAuth: true,
requireRoles: ['superuser']
}
}
export async function GET(request: Request) {
// Handler for GET requests
}
export async function POST(request: Request) {
// Handler for POST requests
}
export async function PUT(request: Request) {
// Handler for PUT requests
}
export async function DELETE(request: Request) {
// Handler for DELETE requests
}
Legacy Metadata Format
For backward compatibility, you can also use the legacy format:
// Legacy format (still supported)
export const requireAuth = true
export const requireRoles = ['admin']
Authentication & Authorization
How It Works
- Page Routes: Authentication is checked when the page is accessed
- API Routes: Authentication is checked for each HTTP method based on metadata
- Automatic Redirects: Unauthenticated users are redirected to login
- Role-Based Access: Users must have required roles to access protected resources
Authentication Context
When authenticated, the following context is available:
interface AuthContext {
sub: string // User ID
tenantId: string // Tenant ID
orgId: string // Organization ID
email: string // User email
roles: string[] // User roles
}
Accessing Auth Context
In API routes:
export async function GET(request: Request) {
const container = await createRequestContainer()
const auth = await getAuthFromCookies()
if (!auth) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
}
// Use auth context
console.log('User ID:', auth.sub)
console.log('Organization:', auth.orgId)
console.log('Roles:', auth.roles)
}
In page components:
import { getAuthFromCookies } from '@/lib/auth/server'
export default async function ProtectedPage() {
const auth = await getAuthFromCookies()
if (!auth) {
redirect('/login')
}
return <div>Welcome, {auth.email}!</div>
}
Metadata Configuration
Page Metadata
Pages support comprehensive metadata for navigation and access control:
export const metadata = {
// Authentication
requireAuth: true,
requireRoles: ['admin'] as const,
// Navigation
pageTitle: 'User Management',
pageGroup: 'Administration',
pageOrder: 10,
icon: 'Users',
// Visibility
navHidden: false,
visible: true,
enabled: true
}
API Metadata
API routes support per-method metadata:
export const metadata = {
GET: {
requireAuth: true,
requireRoles: ['admin', 'user']
},
POST: {
requireAuth: true,
requireRoles: ['admin']
},
PUT: {
requireAuth: false // Public endpoint
},
DELETE: {
requireAuth: true,
requireRoles: ['superuser']
}
}
Best Practices
1. Use Appropriate Authentication Levels
// Public pages - no authentication
export const metadata = {
requireAuth: false
}
// Admin-only pages
export const metadata = {
requireAuth: true,
requireRoles: ['admin']
}
// Multi-role access
export const metadata = {
requireAuth: true,
requireRoles: ['admin', 'manager', 'user']
}
2. Organize Pages with Groups
// Administration pages
export const metadata = {
pageGroup: 'Administration',
pageOrder: 10
}
// User management pages
export const metadata = {
pageGroup: 'User Management',
pageOrder: 20
}
3. Use Per-Method API Authorization
export const metadata = {
GET: {
requireAuth: true,
requireRoles: ['admin', 'user'] // Read access for both
},
POST: {
requireAuth: true,
requireRoles: ['admin'] // Write access for admins only
},
DELETE: {
requireAuth: true,
requireRoles: ['superuser'] // Delete access for superusers only
}
}
4. Handle Errors Gracefully
export async function GET(request: Request) {
try {
// Your logic here
return NextResponse.json({ data: result })
} catch (error) {
console.error('API Error:', error)
return NextResponse.json(
{ error: 'Internal Server Error' },
{ status: 500 }
)
}
}
5. Use TypeScript for Type Safety
interface TodoResponse {
items: Todo[]
total: number
page: number
pageSize: number
}
export async function GET(request: Request): Promise<Response> {
const result: TodoResponse = {
items: todos,
total: todos.length,
page: 1,
pageSize: 50
}
return NextResponse.json(result)
}
Examples
Complete Todo Management System
Backend Page:
// src/modules/example/backend/todos/page.tsx
import { Page, PageHeader, PageBody } from '@open-mercato/ui/backend/Page'
import TodosTable from '../../components/TodosTable'
export default function TodosPage() {
return (
<Page>
<PageHeader
title="Todos"
description="Manage your tasks with custom fields"
/>
<PageBody>
<TodosTable />
</PageBody>
</Page>
)
}
Page Metadata:
// src/modules/example/backend/todos/page.meta.ts
export const metadata = {
requireAuth: true,
requireRoles: ['admin'] as const,
pageTitle: 'Todos',
pageGroup: 'Example',
pageOrder: 20,
icon: 'CheckSquare'
}
API Route:
// src/modules/example/api/todos/route.ts
import { createRequestContainer } from '@/lib/di/container'
import { getAuthFromCookies } from '@/lib/auth/server'
import { E } from '@open-mercato/example/datamodel/entities'
import type { QueryEngine } from '@open-mercato/shared/lib/query/types'
export const metadata = {
GET: {
requireAuth: true,
requireRoles: ['admin']
},
POST: {
requireAuth: true,
requireRoles: ['admin', 'superuser']
}
}
export async function GET(request: Request) {
try {
const container = await createRequestContainer()
const queryEngine = container.resolve<QueryEngine>('queryEngine')
const url = new URL(request.url)
const page = parseInt(url.searchParams.get('page') || '1')
const pageSize = parseInt(url.searchParams.get('pageSize') || '50')
const todos = await queryEngine.find('todo', {
pagination: { page, pageSize },
filters: {
// Add filters based on query parameters
}
})
return NextResponse.json({
items: todos.items,
total: todos.total,
page,
pageSize,
totalPages: Math.ceil(todos.total / pageSize)
})
} catch (error) {
console.error('Error fetching todos:', error)
return NextResponse.json(
{ error: 'Internal Server Error' },
{ status: 500 }
)
}
}
This comprehensive guide covers all aspects of creating routes and pages in the Open Mercato framework. For more specific examples, see the API data fetching tutorial.