Add & Replace Routes
Learn how to define URLs, handle parameters, and override existing routes using JopiJS's file-based routing system.
Overview
Routing in JopiJS is file-based. Each folder inside a module's @routes/ directory automatically becomes a public URL. Because JopiJS is modular, routes from different modules are merged into a single routing tree at runtime.
In this guide, you will learn how to:
- Create simple, parameterized, and catch-all routes.
- Attach UI Pages and API Listeners to the same URL.
- Merge and replace routes from different modules.
- Use marker files to manage caching and security metadata.
1. Route Types
JopiJS supports three types of route segments to handle different URL patterns.
A. Simple Routes
The most common type. The folder name matches the URL path exactly.
- File:
src/mod_main/@routes/about/us/page.tsx - URL:
/about/us
B. Parameterized Routes [name]
Used for dynamic URLs where a segment can be any value (e.g., product IDs, usernames).
- File:
src/mod_shop/@routes/product/[id]/page.tsx - URL:
/product/123,/product/phone-case
import { JopiPageProps } from "jopijs/ui";
export default function ProductPage({ params }: JopiPageProps) {
const { id } = params; // Retrieve the [id] from the URL
return <div>Displaying Product: {id}</div>;
}C. Catch-all Routes [...]
Captures all remaining segments of a URL. Useful for serving files or building localized sub-sites.
- File:
src/mod_docs/@routes/help/[...]/page.tsx - URL:
/help/getting-started,/help/api/reference/v1
2. Attaching Listeners (Page vs API)
Each route folder can contain two types of "Listeners":
- UI Page (
page.tsx): A React component that renders the HTML for GET requests. - API Handlers (
onGET.ts,onPOST.ts, etc.): Functions that handle specific HTTP methods.
| File | Handles | Priority |
|---|---|---|
page.tsx | HTTP GET (renders HTML) | Lowest |
onGET.ts | HTTP GET (returns JSON/Data) | Overrides page.tsx |
onPOST.ts | HTTP POST | N/A |
onPUT.ts | HTTP PUT | N/A |
import { JopiRequest } from "jopijs";
export default async function(req: JopiRequest) {
const data = await req.req_getBodyData();
return req.res_jsonResponse({ success: true, received: data });
}Important: A single route cannot have both a page.tsx and an onGET.ts.
If both exist, onGET.ts will take priority and the HTML page will never be rendered.
3. Merging & Replacing Routes
Since JopiJS is modular, multiple modules can contribute to the same route.
Scenario: Merging
mod_authdefines/profilewith apage.tsx.mod_settingsdefines/profilewith anonPOST.ts.- Result:
/profilenow handles both displaying the page (GET) and saving data (POST).
Scenario: Replacing (Priority)
If two modules define the same listener (e.g., both provide a page.tsx for /home), JopiJS uses the Priority System to decide which one to use.
mod_base_theme/
└── @routes/home/page.tsx (default.priority)
mod_custom_theme/
└── @routes/home/
├── page.tsx (high.priority)
└── high.priorityThe version in mod_custom_theme will be served to all users.
4. Route Metadata (Marker Files)
You can control the behavior of a route by adding Marker Files (empty files with specific extensions) inside the route folder.
Caching
autoCache.enable: Forces the page to be cached (recommended for speed).autoCache.disable: Disables caching (not recommended).
Security & Roles
JopiJS uses Marker Files to protect your routes. These files act as gates: if the user doesn't have the required role, the request is rejected before reaching your code.
needRole_[roleName].cond: The Global marker. It applies to all HTTP methods (GET, POST, etc.) and the HTML page.- Method-Specific Markers: You can target specific handlers by adding a prefix. These markers only apply to their respective methods.
Role Mapping Table
| Marker Prefix | Target | Scope |
|---|---|---|
needRole_ | All handlers (page.tsx, onPOST, etc.) | Global |
getNeedRole_ | page.tsx or onGET.ts | Specific to GET |
postNeedRole_ | onPOST.ts | Specific to POST |
putNeedRole_ | onPUT.ts | Specific to PUT |
deleteNeedRole_ | onDELETE.ts | Specific to DELETE |
Example Scenario:
You have a /blog route where everyone can read (no global role), but only editors can post.
@routes/blog/
├── page.tsx # Accessible to all (no global role)
├── onPOST.ts # Handler to create a post
└── postNeedRole_editor.cond # Protects ONLY the POST methodIf you wanted to protect the entire blog (no read, no write) for everyone but admins, you would use:
needRole_admin.cond(Global protection)
For more information on managing users and complex access rules, see the Limiting Access guide.
Inheritance: When you replace a route using the priority system, JopiJS also uses the metadata files (Cache and Security) from the winning module. This only for the replaced items.