An open API service indexing awesome lists of open source software.

https://github.com/g4rcez/brouther

The brother router to help in React apps
https://github.com/g4rcez/brouther

browser-router history react react-router router

Last synced: 2 months ago
JSON representation

The brother router to help in React apps

Awesome Lists containing this project

README

          

# Brouther

A type-safe router for React applications that puts TypeScript first, ensuring your routes, parameters, and query strings are always in sync with your code.

![Version](https://img.shields.io/npm/v/brouther?style=flat-square)
![Downloads](https://img.shields.io/npm/dm/brouther?style=flat-square)
![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue?style=flat-square)
![License](https://img.shields.io/npm/l/brouther?style=flat-square)

## Why Brouther?

When building React applications, keeping your routing configuration in sync with your components can be challenging. URLs change, parameters get renamed, and query strings evolve - but your TypeScript compiler doesn't know about any of it. Until now.

Brouther solves this by creating a **single source of truth** for your routes that TypeScript understands deeply. This means:

- **No more broken links**: If you delete or change a route, TypeScript will show errors everywhere it's used
- **Automatic parameter validation**: Dynamic path parameters like `/user/:id` are type-checked at compile time
- **Type-safe query strings**: Define expected query parameters and their types right in your route definition
- **Zero runtime overhead**: All type checking happens at compile time
- **Incredible developer experience**: Full IntelliSense support for routes, parameters, and query strings

## Installation

```bash
npm install brouther
# or
yarn add brouther
# or
pnpm add brouther
```

## Quick Start

Let's build a simple application to understand how Brouther works:

```typescript
// router.ts
import { createRouter } from 'brouther';
import HomePage from './pages/HomePage';
import UserProfile from './pages/UserProfile';
import ProductList from './pages/ProductList';

// Define your routes with full type information
export const router = createRouter([
{
id: 'home',
path: '/',
element:
},
{
id: 'userProfile',
path: '/user/:userId',
element:
},
{
id: 'products',
path: '/products?category=string&sort=string&page=number',
element:
}
] as const); // The 'as const' is crucial for type inference!
```

Now, let's use it in your application:

```typescript
// App.tsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { Brouther, Outlet } from 'brouther';
import { router } from './router';

function App() {
return (








);
}

// Navigation.tsx
import { Link } from 'brouther';
import { router } from './router';

function Navigation() {
return (

{/* Simple link - no parameters needed */}
Home

{/* Link with path parameter - TypeScript knows userId is required! */}

View Profile

{/* Link with query parameters - all typed! */}

Electronics


);
}
```

## Core Concepts

### Understanding Route Definitions

Brouther uses a special syntax in route paths to define parameters and query strings:

```typescript
// Dynamic path parameters use :paramName
"/user/:userId"; // userId will be a required string parameter

// Query strings are defined after ?
"/products?category=string"; // category is an optional string

// Required query parameters use !
"/products?category=string!"; // category is now required

// Array query parameters use []
"/products?tags=string[]"; // tags is an optional string array

// Combine multiple query parameters with &
"/products?category=string&tags=string[]&inStock=boolean";

// You can even type your parameters
"/post/:postId?published=date&author=string";
```

### Type Safety in Action

Here's where Brouther really shines. Let's look at how TypeScript helps you:

```typescript
// ❌ This will cause a TypeScript error - missing required path
Profile

// ❌ This will also error - wrong parameter name

// ❌ TypeScript catches type mismatches too

// ✅ This is correct - TypeScript is happy!

```

### Accessing Route Data in Components

Brouther provides hooks to access route information with full type safety:

```typescript
// UserProfile.tsx
import { usePaths, useQueryString } from 'brouther';
import { router } from './router';

function UserProfile() {
// Get typed path parameters
const paths = usePaths(router.links.userProfile);
// paths.userId is typed as string

// For the products page, you'd get typed query parameters
const query = useQueryString(router.links.products);
// query.category is string | undefined
// query.page is number | undefined
// query.sort is string | undefined

return

User ID: {paths.userId}
;
}
```

## Advanced Features

### Loaders and Actions

Brouther supports data loading and form actions, similar to modern routing libraries but with full type safety:

```typescript
const router = createRouter([
{
id: 'userProfile',
path: '/user/:userId',
element: ,
// Loader runs before the component renders
loader: async ({ paths, queryString }) => {
// paths.userId is typed!
const user = await fetchUser(paths.userId);
return jsonResponse(user);
},
// Actions handle form submissions
actions: async () => ({
post: async ({ form, paths }) => {
const formData = formToJson(form);
await updateUser(paths.userId, formData);
return redirectResponse('/success');
}
})
}
]);

// In your component
function UserProfile() {
const data = useDataLoader();
// data is fully typed based on your loader!
}
```

### Error Handling

Brouther provides elegant error handling with error boundaries:

```typescript
const router = createRouter([
{
id: 'userProfile',
path: '/user/:userId',
element: ,
errorElement: , // Shown if loader fails
loadingElement: // Shown while loading
}
]);

// Global error handling
} // For 404s
>

```

### Form Integration

Brouther includes a type-safe Form component that integrates with your routes:

```typescript
import { Form } from 'brouther';

function EditProfile() {
const actions = useFormActions();

return (




{actions.loading ? 'Saving...' : 'Save'}


);
}
```

### Programmatic Navigation

Navigate programmatically with full type safety:

```typescript
function SomeComponent() {
const navigation = useNavigation();

const handleClick = () => {
// Type-safe navigation
navigation.push(
router.link(
router.links.userProfile,
{ userId: '123' } // Required!
)
);
};

return Go to Profile;
}
```

## API Reference

### Creating Routes

#### `createRouter(routes, basename?, options?)`

Creates a router configuration with type-safe routes.

```typescript
const router = createRouter(
[...routes],
"/app", // optional basename
{
sensitiveCase: false, // optional: case-sensitive matching
history: createBrowserHistory, // optional: custom history
}
);
```

#### `createMappedRouter(routeMap, basename?, options?)`

Alternative API using an object instead of array:

```typescript
const router = createMappedRouter({
home: {
path: '/',
element:
},
userProfile: {
path: '/user/:userId',
element:
}
} as const);
```

### Hooks

#### `usePaths(routePath)`

Get typed path parameters from the current route.

#### `useQueryString(routePath)`

Get typed query string parameters from the current route.

#### `useNavigation()`

Get navigation methods (push, replace, back, forward).

#### `useDataLoader()`

Get data from the route loader with full type inference.

#### `useFormActions()`

Get form action state (loading, result, etc.).

#### `useErrorPage()`

Get any route errors that occurred.

#### `useLoadingState()`

Check if route is currently loading.

### Components

#### ``

The main provider component that enables routing.

#### ``

Renders the matched route element.

#### ``

Type-safe link component with automatic parameter validation.

#### ``

Type-safe form component that integrates with route actions.

#### ``

Declarative redirect component.

## Best Practices

### 1. Always Use `as const`

This is crucial for TypeScript to infer literal types:

```typescript
// ✅ Good
const router = createRouter([...] as const);

// ❌ Bad - loses type information
const router = createRouter([...]);
```

### 2. Centralize Your Router

Keep your router definition in a single file and export it:

```typescript
// router.ts
export const router = createRouter([...] as const);
export const { links, link, useQueryString, usePaths } = router;
```

### 3. Use Type-Safe Query Strings

Define query string types in your routes for better safety:

```typescript
// Instead of handling raw strings
const searchParams = new URLSearchParams(location.search);
const page = parseInt(searchParams.get("page") || "1");

// Use Brouther's typed approach
const { page = 1 } = useQueryString(router.links.products);
// page is already a number!
```

### 4. Leverage IntelliSense

Your IDE will autocomplete route names, parameters, and query strings. Use this to explore available options and catch errors early.

## Migration Guide

### From React Router

```typescript
// React Router
} />
Profile

// Brouther
{
id: 'user',
path: '/user/:id',
element:
}
Profile
```

### From Next.js

```typescript
// Next.js
Posts

// Brouther
Posts
```

## Examples

Check out our [examples directory](./examples) for complete applications:

- **Basic Blog**: Simple blog with posts and comments
- **E-commerce**: Product catalog with filtering and search
- **Dashboard**: Admin panel with authentication and guards
- **Real-world App**: Full-featured application with all Brouther features

## Contributing

We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.

## License

MIT © [Brouther Contributors](LICENSE)

---

## Need Help?

- 📚 [Full Documentation](https://brouther.dev)
- 🐛 [Issue Tracker](https://github.com/g4rcez/brouther/issues)