{"id":48865935,"url":"https://github.com/g4rcez/brouther","last_synced_at":"2026-04-15T18:30:57.552Z","repository":{"id":39866714,"uuid":"343955063","full_name":"g4rcez/brouther","owner":"g4rcez","description":"The brother router to help in React apps","archived":false,"fork":false,"pushed_at":"2025-11-04T06:22:57.000Z","size":1808,"stargazers_count":24,"open_issues_count":4,"forks_count":1,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-11-04T08:24:15.123Z","etag":null,"topics":["browser-router","history","react","react-router","router"],"latest_commit_sha":null,"homepage":"https://brouther.vercel.app","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/g4rcez.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2021-03-03T00:43:35.000Z","updated_at":"2025-09-30T04:23:41.000Z","dependencies_parsed_at":"2023-10-03T04:04:00.970Z","dependency_job_id":"344a7041-84ae-4bdc-8309-a561c78550df","html_url":"https://github.com/g4rcez/brouther","commit_stats":null,"previous_names":[],"tags_count":13,"template":false,"template_full_name":null,"purl":"pkg:github/g4rcez/brouther","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/g4rcez%2Fbrouther","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/g4rcez%2Fbrouther/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/g4rcez%2Fbrouther/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/g4rcez%2Fbrouther/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/g4rcez","download_url":"https://codeload.github.com/g4rcez/brouther/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/g4rcez%2Fbrouther/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31854580,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-15T15:24:51.572Z","status":"ssl_error","status_checked_at":"2026-04-15T15:24:39.138Z","response_time":63,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["browser-router","history","react","react-router","router"],"created_at":"2026-04-15T18:30:56.820Z","updated_at":"2026-04-15T18:30:57.544Z","avatar_url":"https://github.com/g4rcez.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Brouther\n\nA type-safe router for React applications that puts TypeScript first, ensuring your routes, parameters, and query strings are always in sync with your code.\n\n![Version](https://img.shields.io/npm/v/brouther?style=flat-square)\n![Downloads](https://img.shields.io/npm/dm/brouther?style=flat-square)\n![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue?style=flat-square)\n![License](https://img.shields.io/npm/l/brouther?style=flat-square)\n\n## Why Brouther?\n\nWhen 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.\n\nBrouther solves this by creating a **single source of truth** for your routes that TypeScript understands deeply. This means:\n\n- **No more broken links**: If you delete or change a route, TypeScript will show errors everywhere it's used\n- **Automatic parameter validation**: Dynamic path parameters like `/user/:id` are type-checked at compile time\n- **Type-safe query strings**: Define expected query parameters and their types right in your route definition\n- **Zero runtime overhead**: All type checking happens at compile time\n- **Incredible developer experience**: Full IntelliSense support for routes, parameters, and query strings\n\n## Installation\n\n```bash\nnpm install brouther\n# or\nyarn add brouther\n# or\npnpm add brouther\n```\n\n## Quick Start\n\nLet's build a simple application to understand how Brouther works:\n\n```typescript\n// router.ts\nimport { createRouter } from 'brouther';\nimport HomePage from './pages/HomePage';\nimport UserProfile from './pages/UserProfile';\nimport ProductList from './pages/ProductList';\n\n// Define your routes with full type information\nexport const router = createRouter([\n  {\n    id: 'home',\n    path: '/',\n    element: \u003cHomePage /\u003e\n  },\n  {\n    id: 'userProfile',\n    path: '/user/:userId',\n    element: \u003cUserProfile /\u003e\n  },\n  {\n    id: 'products',\n    path: '/products?category=string\u0026sort=string\u0026page=number',\n    element: \u003cProductList /\u003e\n  }\n] as const); // The 'as const' is crucial for type inference!\n```\n\nNow, let's use it in your application:\n\n```typescript\n// App.tsx\nimport React from 'react';\nimport ReactDOM from 'react-dom/client';\nimport { Brouther, Outlet } from 'brouther';\nimport { router } from './router';\n\nfunction App() {\n  return (\n    \u003cBrouther config={router.config}\u003e\n      \u003cdiv className=\"app\"\u003e\n        \u003cNavigation /\u003e\n        \u003cmain\u003e\n          \u003cOutlet /\u003e\n        \u003c/main\u003e\n      \u003c/div\u003e\n    \u003c/Brouther\u003e\n  );\n}\n\n// Navigation.tsx\nimport { Link } from 'brouther';\nimport { router } from './router';\n\nfunction Navigation() {\n  return (\n    \u003cnav\u003e\n      {/* Simple link - no parameters needed */}\n      \u003cLink href={router.links.home}\u003eHome\u003c/Link\u003e\n\n      {/* Link with path parameter - TypeScript knows userId is required! */}\n      \u003cLink\n        href={router.links.userProfile}\n        paths={{ userId: '123' }}\n      \u003e\n        View Profile\n      \u003c/Link\u003e\n\n      {/* Link with query parameters - all typed! */}\n      \u003cLink\n        href={router.links.products}\n        query={{\n          category: 'electronics',\n          sort: 'price',\n          page: 1\n        }}\n      \u003e\n        Electronics\n      \u003c/Link\u003e\n    \u003c/nav\u003e\n  );\n}\n```\n\n## Core Concepts\n\n### Understanding Route Definitions\n\nBrouther uses a special syntax in route paths to define parameters and query strings:\n\n```typescript\n// Dynamic path parameters use :paramName\n\"/user/:userId\"; // userId will be a required string parameter\n\n// Query strings are defined after ?\n\"/products?category=string\"; // category is an optional string\n\n// Required query parameters use !\n\"/products?category=string!\"; // category is now required\n\n// Array query parameters use []\n\"/products?tags=string[]\"; // tags is an optional string array\n\n// Combine multiple query parameters with \u0026\n\"/products?category=string\u0026tags=string[]\u0026inStock=boolean\";\n\n// You can even type your parameters\n\"/post/:postId?published=date\u0026author=string\";\n```\n\n### Type Safety in Action\n\nHere's where Brouther really shines. Let's look at how TypeScript helps you:\n\n```typescript\n// ❌ This will cause a TypeScript error - missing required path\n\u003cLink href={router.links.userProfile}\u003eProfile\u003c/Link\u003e\n\n// ❌ This will also error - wrong parameter name\n\u003cLink\n  href={router.links.userProfile}\n  paths={{ id: '123' }} // Should be userId!\n\u003e\n\n// ❌ TypeScript catches type mismatches too\n\u003cLink\n  href={router.links.products}\n  query={{ page: '1' }} // Should be a number!\n\u003e\n\n// ✅ This is correct - TypeScript is happy!\n\u003cLink\n  href={router.links.userProfile}\n  paths={{ userId: '123' }}\n\u003e\n```\n\n### Accessing Route Data in Components\n\nBrouther provides hooks to access route information with full type safety:\n\n```typescript\n// UserProfile.tsx\nimport { usePaths, useQueryString } from 'brouther';\nimport { router } from './router';\n\nfunction UserProfile() {\n  // Get typed path parameters\n  const paths = usePaths(router.links.userProfile);\n  // paths.userId is typed as string\n\n  // For the products page, you'd get typed query parameters\n  const query = useQueryString(router.links.products);\n  // query.category is string | undefined\n  // query.page is number | undefined\n  // query.sort is string | undefined\n\n  return \u003cdiv\u003eUser ID: {paths.userId}\u003c/div\u003e;\n}\n```\n\n## Advanced Features\n\n### Loaders and Actions\n\nBrouther supports data loading and form actions, similar to modern routing libraries but with full type safety:\n\n```typescript\nconst router = createRouter([\n  {\n    id: 'userProfile',\n    path: '/user/:userId',\n    element: \u003cUserProfile /\u003e,\n    // Loader runs before the component renders\n    loader: async ({ paths, queryString }) =\u003e {\n      // paths.userId is typed!\n      const user = await fetchUser(paths.userId);\n      return jsonResponse(user);\n    },\n    // Actions handle form submissions\n    actions: async () =\u003e ({\n      post: async ({ form, paths }) =\u003e {\n        const formData = formToJson(form);\n        await updateUser(paths.userId, formData);\n        return redirectResponse('/success');\n      }\n    })\n  }\n]);\n\n// In your component\nfunction UserProfile() {\n  const data = useDataLoader\u003ctypeof loader\u003e();\n  // data is fully typed based on your loader!\n}\n```\n\n### Error Handling\n\nBrouther provides elegant error handling with error boundaries:\n\n```typescript\nconst router = createRouter([\n  {\n    id: 'userProfile',\n    path: '/user/:userId',\n    element: \u003cUserProfile /\u003e,\n    errorElement: \u003cUserErrorPage /\u003e, // Shown if loader fails\n    loadingElement: \u003cUserSkeleton /\u003e // Shown while loading\n  }\n]);\n\n// Global error handling\n\u003cBrouther\n  config={router.config}\n  ErrorElement={\u003cNotFoundPage /\u003e} // For 404s\n\u003e\n  \u003cApp /\u003e\n\u003c/Brouther\u003e\n```\n\n### Form Integration\n\nBrouther includes a type-safe Form component that integrates with your routes:\n\n```typescript\nimport { Form } from 'brouther';\n\nfunction EditProfile() {\n  const actions = useFormActions();\n\n  return (\n    \u003cForm method=\"post\"\u003e\n      \u003cinput name=\"name\" /\u003e\n      \u003cinput name=\"email\" type=\"email\" /\u003e\n      \u003cbutton type=\"submit\"\u003e\n        {actions.loading ? 'Saving...' : 'Save'}\n      \u003c/button\u003e\n    \u003c/Form\u003e\n  );\n}\n```\n\n### Programmatic Navigation\n\nNavigate programmatically with full type safety:\n\n```typescript\nfunction SomeComponent() {\n  const navigation = useNavigation();\n\n  const handleClick = () =\u003e {\n    // Type-safe navigation\n    navigation.push(\n      router.link(\n        router.links.userProfile,\n        { userId: '123' } // Required!\n      )\n    );\n  };\n\n  return \u003cbutton onClick={handleClick}\u003eGo to Profile\u003c/button\u003e;\n}\n```\n\n## API Reference\n\n### Creating Routes\n\n#### `createRouter(routes, basename?, options?)`\n\nCreates a router configuration with type-safe routes.\n\n```typescript\nconst router = createRouter(\n    [...routes],\n    \"/app\", // optional basename\n    {\n        sensitiveCase: false, // optional: case-sensitive matching\n        history: createBrowserHistory, // optional: custom history\n    }\n);\n```\n\n#### `createMappedRouter(routeMap, basename?, options?)`\n\nAlternative API using an object instead of array:\n\n```typescript\nconst router = createMappedRouter({\n  home: {\n    path: '/',\n    element: \u003cHomePage /\u003e\n  },\n  userProfile: {\n    path: '/user/:userId',\n    element: \u003cUserProfile /\u003e\n  }\n} as const);\n```\n\n### Hooks\n\n#### `usePaths(routePath)`\n\nGet typed path parameters from the current route.\n\n#### `useQueryString(routePath)`\n\nGet typed query string parameters from the current route.\n\n#### `useNavigation()`\n\nGet navigation methods (push, replace, back, forward).\n\n#### `useDataLoader()`\n\nGet data from the route loader with full type inference.\n\n#### `useFormActions()`\n\nGet form action state (loading, result, etc.).\n\n#### `useErrorPage()`\n\nGet any route errors that occurred.\n\n#### `useLoadingState()`\n\nCheck if route is currently loading.\n\n### Components\n\n#### `\u003cBrouther\u003e`\n\nThe main provider component that enables routing.\n\n#### `\u003cOutlet\u003e`\n\nRenders the matched route element.\n\n#### `\u003cLink\u003e`\n\nType-safe link component with automatic parameter validation.\n\n#### `\u003cForm\u003e`\n\nType-safe form component that integrates with route actions.\n\n#### `\u003cRedirect\u003e`\n\nDeclarative redirect component.\n\n## Best Practices\n\n### 1. Always Use `as const`\n\nThis is crucial for TypeScript to infer literal types:\n\n```typescript\n// ✅ Good\nconst router = createRouter([...] as const);\n\n// ❌ Bad - loses type information\nconst router = createRouter([...]);\n```\n\n### 2. Centralize Your Router\n\nKeep your router definition in a single file and export it:\n\n```typescript\n// router.ts\nexport const router = createRouter([...] as const);\nexport const { links, link, useQueryString, usePaths } = router;\n```\n\n### 3. Use Type-Safe Query Strings\n\nDefine query string types in your routes for better safety:\n\n```typescript\n// Instead of handling raw strings\nconst searchParams = new URLSearchParams(location.search);\nconst page = parseInt(searchParams.get(\"page\") || \"1\");\n\n// Use Brouther's typed approach\nconst { page = 1 } = useQueryString(router.links.products);\n// page is already a number!\n```\n\n### 4. Leverage IntelliSense\n\nYour IDE will autocomplete route names, parameters, and query strings. Use this to explore available options and catch errors early.\n\n## Migration Guide\n\n### From React Router\n\n```typescript\n// React Router\n\u003cRoute path=\"/user/:id\" element={\u003cUser /\u003e} /\u003e\n\u003cLink to={`/user/${userId}`}\u003eProfile\u003c/Link\u003e\n\n// Brouther\n{\n  id: 'user',\n  path: '/user/:id',\n  element: \u003cUser /\u003e\n}\n\u003cLink href={router.links.user} paths={{ id: userId }}\u003eProfile\u003c/Link\u003e\n```\n\n### From Next.js\n\n```typescript\n// Next.js\n\u003cLink href={`/user/${userId}?tab=posts`}\u003ePosts\u003c/Link\u003e\n\n// Brouther\n\u003cLink\n  href={router.links.user}\n  paths={{ userId }}\n  query={{ tab: 'posts' }}\n\u003ePosts\u003c/Link\u003e\n```\n\n## Examples\n\nCheck out our [examples directory](./examples) for complete applications:\n\n- **Basic Blog**: Simple blog with posts and comments\n- **E-commerce**: Product catalog with filtering and search\n- **Dashboard**: Admin panel with authentication and guards\n- **Real-world App**: Full-featured application with all Brouther features\n\n## Contributing\n\nWe welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.\n\n## License\n\nMIT © [Brouther Contributors](LICENSE)\n\n---\n\n## Need Help?\n\n- 📚 [Full Documentation](https://brouther.dev)\n- 🐛 [Issue Tracker](https://github.com/g4rcez/brouther/issues)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fg4rcez%2Fbrouther","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fg4rcez%2Fbrouther","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fg4rcez%2Fbrouther/lists"}