t-router
is a dynamic routing library for React with Vite, designed to handle routes dynamically based on your file structure, with the option to apply middleware for route protection or other logic. It supports both TypeScript and JavaScript files and integrates seamlessly with React Router.
To use t-router
in your project, you can install it via npm or yarn.
Learn more about t-react
npx create-treact
npm install @tomkoooo/t-router@latest
yarn add @tomkoooo/t-router@latest
import React from 'react';
import { DynamicRouter, Link } from '@tomkoooo/t-router';
const App = () => {
const pagesConfig = {
'/home': {
type: 'public'
},
'/profile': {
type: 'private',
credentials: 'user.role === "admin"',
redirectTo: '/login'
}
};
/*
or use import routes './your-route-config.json'
{
"Routes" : {
"/": {
"type": "public"
}
}
}
<DynamicRouter pagesConfig={routes.Routes} pages={pages}/>
*/
const pages = import.meta.glob('./pages/**/*.{tsx,jsx}') //Needs to be passed like this because from node_modules the package ahs no access for glob but this creates opportunitys to use it outside Vite
return (
<DynamicRouter pagesConfig={pagesConfig} pages={pages}/>
{/*There is a built-in middlewere by default that handles the pagesConfig logic, by default every route will be public, but for the user credentials to work you need to pass a user object*/}
);
};
export default App;
You can create ExpressJs
server with API routes as folder structure. With this setup you will be able to create API routes like in NextJs
. For this structure follow the example structure as provided. Also on run the server will console.log the read API structure with the API routes. Becouse file reading not workin from the node_modules
here is the server.ts
you can setup easily:
Example setup with Vite:
// ./server/iindex.ts
import express, { Request, Response, NextFunction } from 'express';
import path from 'path';
import fs from 'fs/promises';
import { fileURLToPath } from 'url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const app = express();
const PORT = process.env.PORT || 4000;
// Dynamically load API routes based on filesystem structure
const loadRoutes = async (dir: string, parentPath: string = '') => {
const files = await fs.readdir(dir, { withFileTypes: true });
for (const file of files) {
const fullPath = path.join(dir, file.name);
const routePath = `${parentPath}/${file.name.replace(/\.[tj]s$/, '')}`;
if (file.isDirectory()) {
// Recursively load files within directories
await loadRoutes(fullPath, routePath);
} else if (file.name === 'route.ts' || file.name === 'route.js') {
// Process only files named `route.ts` or `route.js`
const expressPath = `/api${parentPath.replace(/\[(\w+)\]/g, ':$1')}`; // Convert `[param]` to `:param`
// Import the route handler
const routeHandler = await import(fullPath);
// Apply route handler, assuming it exports a default function
app.use(expressPath, async (req: Request, res: Response, next: NextFunction) => {
try {
await routeHandler.default(req, res);
} catch (err) {
next(err);
}
});
console.log(`Loaded route: [${expressPath}] -> ${fullPath}`);
}
}
};
// Enable CORS for all routes
app.use(function (req, res, next) {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept');
next();
});
// Load API routes from 'server/api' folder
loadRoutes(path.join(__dirname, 'api'))
.then(() => {
app.listen(PORT, () => {
console.log(`Server is running on http://localhost:${PORT}`);
});
})
.catch(console.error);
// vite.config.js
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
server: { //this will redirect every API call to the Express server without using http:localhost:3000/api/*
proxy: {
'/api': {
target: 'http://localhost:4000', // Target Express server
changeOrigin: true, // Handles the origin to match the target
}
}
}
})
By this you can create the API folder inside Server and then add routes like Login/
create a exported HTTP handler in a route.ts/js
file and you can call it like ('/api/login')
. Or also you can create dynamic endpoints with folder name between []
like [test]
// ./server/[test]/route.ts
import { Request, Response } from 'express';
export default async (req: Request, res: Response) => {
const { test } = req.params;
// A logika itt történik, pl. adatbázis lekérdezés
res.json({ message: `Hello world, given params: ${test}` });
};
To run the server install nodemon
so you not need to restart everytime you make changes. Then create nodemon.json
and make a script for the server start:
// nodemon.json for typescript
{
"watch": ["server", "src"],
"ext": "js,ts,json",
"ignore": ["node_modules"],
"exec": "npx tsx server/index.ts"
}
// extend the scripts in package.json (start:server)
"scripts": {
"start:server": "nodemon server/index.ts",
"dev": "vite",
"build": "tsc -b && vite build",
"lint": "eslint .",
"preview": "vite preview"
},
t-router
uses import.meta.glob
to dynamically import your pages based on the file structure but you need to call the glob
function and pass the reasult as pages because it's impossible to do from node_modules
folder. Files should be placed under the pages
directory and named page.js
, page.jsx
, page.ts
, or page.tsx
. For the root create a page
file in the root of the pages
directory and it will match the /
route.
server/
api/
login/
route.ts
[id]/
route.ts
index.ts
src/
pages/
home/
page.tsx
profile/
page.tsx
[id]/
page.tsx
page.tsx
The router will automatically pick up these routes and map them to the URL paths /home
and /profile
. And also can handle dynamic routes like [id] or [name].
Create dynamic URls with braces. The router automaticly handles them and you can access the param in the component like this:
// src/pages/blog/[id]/page.tsx
import React from 'react';
import { useDynamicParams } from '@tomkoooo/t-router';
const BlogPostPage: React.FC = () => {
const { id } = useDynamicParams(); //you need to use the dynamic name you given in the braces
return <h1>Blog Post ID: {id}</h1>;
};
export default BlogPostPage;
You can pass a custom middleware component to the DynamicRouter
.
import { DynamicRouter, Link } from '@tomkoooo/t-router';
import MyCustomMiddleware from './MyCustomMiddleware';
import routes from './routes.json'
const App = () => {
const pages = import.meta.glob('./pages/**/*.{tsx,jsx}')
return (
<DynamicRouter pagesConfig={routes.Routes} pages={pages} middlewere={MyCustomMiddleware}/>
);
};
export default App;
You can pass a custom user
object to the router, which can be used for route authentication.
const App = () => {
const user = {
id: '1',
name: 'John Doe',
role: 'admin'
};
return (
<DynamicRouter pagesConfig={pagesConfig} pages={pages} user={user}/>
{/*You can pass user object for the built-in middlewere to work with it for private routing*/}
);
};
t-router
provides a custom Link
component that works with React Router and supports passing additional props like className
and target
.
import { Link } from '@tomkoooo/t-router';
const MyComponent = () => {
return (
<div>
<Link to="/profile" className="nav-link">
Go to Profile
</Link>
</div>
);
};
t-router
includes a default NotFound
page that automatically handles 404 errors on the router but can be imported.
import NotFound from '@tomkoooo/t-router/dist/pages/NotFound';
const App = () => {
return (
<NotFound />
);
};
-
pages
(required): All the pages in thepages
folder. -
pagesConfig
(required): An object defining routes and their configurations. Each route can have atype
, optionalcredentials
condition, and aredirectTo
URL. -
user
(optional): A custom user object to be passed for route authentication. -
middlewere
(optional): A custom middleware component to handle additional logic such as authentication or redirects. -
loadingFallback
(optional): A fallback element displayed while the route components are loading.
to
(required): The path to navigate to.children
(required): The content displayed inside the link.className
(optional): Additional CSS class for styling.target
(optional): The target for opening the link (e.g.,_blank
).
This project is licensed under the MIT License.
Let me know if you need further adjustments! Github/Tomkoooo