This guide explains how to add a new route, page, or component to the application using our router setup.
Our application uses a Single Page Application (SPA) router with helper utilities that simplify adding and managing routes. The router handles page transitions, authentication checks, and subscription requirements with minimal code.
First, create an HTML file for your new page in the /views directory:
<!-- /views/my-new-feature.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My New Feature</title>
<link rel="stylesheet" href="/css/main.css">
</head>
<body>
<div class="my-feature-container">
<h1 data-i18n="my_feature.title">My New Feature</h1>
<p data-i18n="my_feature.description">This is my new feature page.</p>
<!-- Your page content here -->
<div class="feature-content">
<form id="my-feature-form">
<!-- Form fields -->
<button type="submit" data-i18n="my_feature.submit">Submit</button>
</form>
</div>
</div>
</body>
</html>If your page needs JavaScript initialization (event listeners, data loading, etc.), create an initializer function in page-initializers.js:
/**
* Initialize the new feature page
*/
export function initMyNewFeaturePage() {
console.log('Initializing my new feature page');
// Get elements
const form = document.getElementById('my-feature-form');
if (!form) {
console.error('My feature form not found');
return;
}
// Add event listeners
form.addEventListener('submit', async (e) => {
e.preventDefault();
// Form submission logic
console.log('Form submitted');
// Get form data
const formData = new FormData(form);
const formDataObj = Object.fromEntries(formData.entries());
// Process the form data
console.log('Form data:', formDataObj);
// Additional logic...
});
// Other initialization code...
}Don't forget to export the initializer function at the top of the file:
export {
// ... existing exports
initMyNewFeaturePage
} from './page-initializers.js';Open router.js and add your new route to the createRoutes call in the defineRoutes function:
// In router.js
export function defineRoutes(router) {
console.log('Defining routes...');
// Define routes using the createRoutes helper
const routes = createRoutes({
// Existing routes...
'/': '/views/home.html',
// Add your new route
'/my-new-feature': {
viewPath: '/views/my-new-feature.html',
afterRender: initMyNewFeaturePage, // Optional
requireAuth: true // If the route requires authentication
}
});
// Rest of the function...
}For better organization in larger applications, you can create a separate file for related routes:
// feature-routes.js
import { createRoutes } from './route-helpers.js';
import { initMyNewFeaturePage } from './page-initializers.js';
export const featureRoutes = createRoutes({
'/my-new-feature': {
viewPath: '/views/my-new-feature.html',
afterRender: initMyNewFeaturePage,
requireAuth: true
},
'/my-new-feature/settings': {
viewPath: '/views/my-new-feature-settings.html',
requireAuth: true
}
});Then import and merge these routes in router.js:
// In router.js
import { featureRoutes } from './feature-routes.js';
export function defineRoutes(router) {
console.log('Defining routes...');
// Define core routes
const coreRoutes = createRoutes({
// Existing routes...
});
// Merge all routes
const routes = {
...coreRoutes,
...featureRoutes
// Add more route collections as needed
};
// Register routes with the router
router.registerRoutes(routes);
// Rest of the function...
}When adding a route, you can use these options:
| Option | Type | Description |
|---|---|---|
viewPath |
String | Path to the HTML view file |
afterRender |
Function | Function to run after the page is rendered |
beforeEnter |
Function | Function to run before entering the route |
requireAuth |
Boolean | Whether the route requires authentication |
requireSubscription |
Boolean | Whether the route requires an active subscription |
'/about': '/views/about.html''/profile': {
viewPath: '/views/profile.html',
afterRender: initProfilePage
}'/dashboard': {
viewPath: '/views/dashboard.html',
requireAuth: true
}'/premium-feature': {
viewPath: '/views/premium-feature.html',
requireSubscription: true
}'/admin': {
viewPath: '/views/admin.html',
beforeEnter: (to, from, next) => {
const userRole = localStorage.getItem('user_role');
if (userRole === 'admin') {
next();
} else {
next('/access-denied');
}
}
}After adding the route, you can test it by navigating to the URL in your browser:
http://localhost:3000/my-new-feature
The router will handle loading the HTML, running the initializer function, and checking authentication if required.
-
Organize Related Routes: Keep related routes together, either in the same section of
router.jsor in a separate routes file. -
Use Descriptive Route Names: Choose route paths that clearly describe the feature or page.
-
Follow Naming Conventions: Use consistent naming for HTML files, initializer functions, and route paths.
-
Handle Authentication Properly: Use the
requireAuthoption for protected routes instead of implementing custom checks. -
Add Internationalization: Use
data-i18nattributes for text that needs to be translated. -
Clean Up Event Listeners: If your initializer adds event listeners, make sure to remove them when the page is unloaded to prevent memory leaks.
-
Test Thoroughly: Test your new route with different scenarios (logged in, logged out, with/without subscription) to ensure it behaves correctly.
The generator script provides tools for creating various components for both client-side and server-side development.
Make sure the script is executable:
chmod +x bin/generator.jsThe generator now uses a category-based command structure:
./bin/generator.js <category> <command> [options]Categories:
client: Client-side generatorsserver: Server-side generators
./bin/generator.js client route --route="/my-feature" --name="My Feature" [--auth] [--subscription]Options:
--route: The route path (required, e.g., "/my-feature")--name: The feature name (required, e.g., "My Feature")--auth: Add this flag if the route requires authentication--subscription: Add this flag if the route requires an active subscription
Example:
./bin/generator.js client route --route="/contact-us" --name="Contact Us"This will:
- Create a new HTML view file at
public/views/contact-us.html - Add an initializer function
initContactUsPagetopublic/js/page-initializers.js - Add the route to
public/js/router.js
./bin/generator.js server route --path="/api/v1/users" --controller="UserController" --method="get"Options:
--path: The API path (required, e.g., "/api/v1/users")--controller: The controller name (required, e.g., "UserController")--method: The HTTP method (required, e.g., "get", "post", "put", "delete", "patch")
Example:
./bin/generator.js server route --path="/api/v1/users" --controller="User" --method="get"This will add a new route to the appropriate route file in the src/routes directory.
./bin/generator.js server migration --name="add_user_fields"Options:
--name: The migration name (required, e.g., "add_user_fields")
Example:
./bin/generator.js server migration --name="add_user_profile_fields"This will create a timestamped SQL migration file in the supabase/migrations directory. The migration file includes clearly separated sections for "up" migrations (changes to apply) and "down" migrations (how to revert the changes).
./bin/generator.js server controller --name="UserController"Options:
--name: The controller name (required, e.g., "UserController" or "User")
Example:
./bin/generator.js server controller --name="User"This will create a new controller file with standard CRUD methods in the src/controllers directory.
For backward compatibility, the old command format is still supported but will show a deprecation warning:
./bin/generator.js route --route="/my-feature" --name="My Feature" # DeprecatedFor general help:
./bin/generator.js --helpFor command-specific help:
./bin/generator.js client route --help
./bin/generator.js server route --help
./bin/generator.js server migration --help
./bin/generator.js server controller --helpAfter running the generator, you can customize the generated files to fit your specific requirements.
The generator script uses a template-based approach for creating files. Templates are stored in the templates directory and are organized by category and type:
templates/
├── client/
│ └── route/
│ ├── view.html.template
│ ├── view.js.template
│ └── initializer.js.template
└── server/
├── controller.js.template
├── route.js.template
└── migration.sql.template
- Separation of Concerns: Templates are separate from the generator code, making maintenance easier
- Consistency: All generated files follow the same structure and style
- Customization: Templates can be modified without changing the generator code
- Flexibility: New templates can be added for additional file types
If you need to modify the structure or content of generated files, you can edit the templates directly. Templates use placeholders like {{kebabCase}}, {{featureName}}, and {{controllerName}} that are replaced with actual values during generation.
For example, to change the structure of generated HTML views, edit templates/client/route/view.html.template.