Skip to main content

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

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 page
  • pageTitle: Title displayed in navigation and page header
  • pageGroup: Group for organizing pages in navigation
  • pageOrder: 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

  1. Page Routes: Authentication is checked when the page is accessed
  2. API Routes: Authentication is checked for each HTTP method based on metadata
  3. Automatic Redirects: Unauthenticated users are redirected to login
  4. 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.