{"id":28090576,"url":"https://github.com/stacksjs/bun-router","last_synced_at":"2026-05-02T01:02:09.329Z","repository":{"id":290450073,"uuid":"974465639","full_name":"stacksjs/bun-router","owner":"stacksjs","description":"🌐 A powerful yet lightweight router for your server.","archived":false,"fork":false,"pushed_at":"2025-05-06T17:41:14.000Z","size":21286,"stargazers_count":3,"open_issues_count":2,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-05-06T18:44:19.971Z","etag":null,"topics":["bun","laravel","router","typescript"],"latest_commit_sha":null,"homepage":"https://bun-router.netlify.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/stacksjs.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":".github/CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE.md","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["stacksjs","chrisbbreuer"],"open_collective":"stacksjs"}},"created_at":"2025-04-28T20:26:04.000Z","updated_at":"2025-05-05T20:59:09.000Z","dependencies_parsed_at":"2025-04-28T22:52:33.334Z","dependency_job_id":"f3954993-b2b2-4657-9dc0-ac0d0160dc45","html_url":"https://github.com/stacksjs/bun-router","commit_stats":null,"previous_names":["stacksjs/bun-router"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stacksjs%2Fbun-router","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stacksjs%2Fbun-router/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stacksjs%2Fbun-router/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stacksjs%2Fbun-router/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stacksjs","download_url":"https://codeload.github.com/stacksjs/bun-router/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253851610,"owners_count":21973773,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["bun","laravel","router","typescript"],"created_at":"2025-05-13T13:01:10.281Z","updated_at":"2026-05-02T01:02:09.320Z","avatar_url":"https://github.com/stacksjs.png","language":"TypeScript","funding_links":["https://github.com/sponsors/stacksjs","https://github.com/sponsors/chrisbbreuer","https://opencollective.com/stacksjs"],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\u003cimg src=\".github/art/cover.jpg\" alt=\"Social Card of this repo\"\u003e\u003c/p\u003e\n\n[![npm version][npm-version-src]][npm-version-href]\n[![GitHub Actions][github-actions-src]][github-actions-href]\n[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)\n\u003c!-- [![npm downloads][npm-downloads-src]][npm-downloads-href] --\u003e\n\u003c!-- [![Codecov][codecov-src]][codecov-href] --\u003e\n\n# bun-router\n\nA high-performance, feature-rich router for Bun applications.\n\n## Features\n\n- Fast and efficient routing system\n- Support for all HTTP methods\n- Path parameters and parameter constraints\n- Middleware support with built-in middleware\n- Group routing, resource routing, and nested routes\n- Named routes and URL generation\n- Domain and subdomain routing\n- CSRF protection and session management\n- Type-safe API\n- Native Bun.serve() integration\n- WebSocket support\n\n## Installation\n\n```bash\nbun add bun-router\n```\n\n## Basic Usage\n\n```typescript\nimport { Router } from 'bun-router'\n\n// Create a router\nconst router = new Router()\n\n// Define routes\nrouter.get('/', () =\u003e new Response('Hello, World!'))\nrouter.post('/users', async (req) =\u003e {\n  const data = await req.json()\n  return Response.json({ message: 'User created', data })\n})\n\n// Start the server\nrouter.serve({\n  port: 3000,\n})\n```\n\n## Route Options\n\n```typescript\n// Route with path parameters\nrouter.get('/users/{id}', (req) =\u003e {\n  const { id } = req.params\n  return Response.json({ id })\n})\n\n// Named routes\nrouter.get('/users/{id}', getUserHandler, 'api', 'users.show')\n\n// Generate URL for named route\nconst url = router.route('users.show', { id: '123' })\n```\n\n## Middleware\n\n```typescript\nimport { cors, jsonBody, Router } from 'bun-router'\n\nconst router = new Router()\n\n// Use middleware globally\nrouter.use(jsonBody())\nrouter.use(cors())\n\n// Or apply to a group of routes\nrouter.group({\n  prefix: '/api',\n  middleware: [jsonBody(), cors()]\n}, () =\u003e {\n  router.get('/users', () =\u003e Response.json({ users: [] }))\n})\n```\n\n### Built-in Middleware\n\n- **Cors** - Handles Cross-Origin Resource Sharing\n- **JsonBody** - Parses JSON request bodies into `req.jsonBody`\n- **RequestId** - Adds unique IDs to requests with `X-Request-ID` header\n- **Session** - Provides session management with `req.session`\n- **Csrf** - Protects against cross-site request forgery\n- **Auth** - Basic authentication middleware\n\n### Creating Custom Middleware\n\n```typescript\nimport { EnhancedRequest, MiddlewareHandler, NextFunction } from 'bun-router'\n\nclass LoggerMiddleware {\n  async handle(req: EnhancedRequest, next: NextFunction): Promise\u003cResponse\u003e {\n    console.log(`${req.method} ${req.url}`)\n    return next()\n  }\n}\n\n// Use custom middleware\nrouter.use(new LoggerMiddleware())\n```\n\n## WebSocket Support\n\nbun-router provides seamless integration with Bun's high-performance WebSocket capabilities:\n\n```typescript\nimport type { ServerWebSocket } from 'bun'\nimport { Router } from 'bun-router'\n\n// Define data type for WebSocket clients\ninterface ClientData {\n  userId: string\n  room: string\n}\n\n// Create a router\nconst router = new Router\u003cClientData\u003e() // Type-safe WebSockets\n\n// Add a regular HTTP route\nrouter.get('/', () =\u003e new Response('WebSocket Server'))\n\n// Configure WebSocket handling\nrouter.websocket({\n  // Handle new connections\n  open(ws) {\n    console.log('Client connected from:', ws.remoteAddress)\n\n    // Set client data (available in all handlers as ws.data)\n    ws.data = { userId: 'user_123', room: 'general' }\n\n    // Subscribe to topics for pub/sub messaging\n    ws.subscribe('general')\n    ws.send('Welcome to the server!')\n  },\n\n  // Handle incoming messages\n  message(ws, message) {\n    // Handle different message types (string, ArrayBuffer, Uint8Array)\n    const content = typeof message === 'string' ? message : 'Binary data received'\n    console.log(`Received from ${ws.data.userId}: ${content}`)\n\n    // Send response and check for backpressure\n    const sendResult = ws.send(`Echo: ${content}`)\n\n    if (sendResult === -1) {\n      console.log('Backpressure detected, message queued')\n    }\n    else if (sendResult === 0) {\n      console.log('Send failed, connection may be closed')\n    }\n    else {\n      console.log(`Sent ${sendResult} bytes`)\n    }\n\n    // Broadcast to all subscribers of a topic (except sender)\n    router.publish('general', `${ws.data.userId}: ${content}`)\n  },\n\n  // Handle disconnections\n  close(ws, code, reason) {\n    console.log(`Client ${ws.data.userId} disconnected: ${reason || 'No reason'} (${code})`)\n    ws.unsubscribe('general')\n  },\n\n  // Handle errors\n  error(ws, error) {\n    console.error(`Error for client ${ws.data.userId}:`, error)\n  },\n\n  // Handle backpressure relief\n  drain(ws) {\n    console.log(`Backpressure relieved for ${ws.data.userId}, socket ready for more data`)\n  },\n\n  // Advanced configuration options\n  maxPayloadLength: 16 _ 1024 _ 1024, // 16MB max message size (default)\n  idleTimeout: 120, // 2 minutes (default)\n  backpressureLimit: 1024 _ 1024, // 1MB (default)\n  closeOnBackpressureLimit: false, // Don't close on backpressure limit (default)\n\n  // Enable per-message compression\n  perMessageDeflate: {\n    compress: '16KB', // Use 16KB compression level\n    decompress: true\n  },\n\n  sendPings: true, // Send ping frames to keep connection alive (default)\n  publishToSelf: false // Don't send published messages to publisher (default)\n})\n\n// Start the server\nrouter.serve({ port: 3000 })\n```\n\n### WebSocket Utility Methods\n\nThe router provides utility methods for working with WebSockets:\n\n```typescript\n// Publish a message to all subscribers of a topic\n// Returns: Number of bytes sent (or negative on error)\nconst result = router.publish('room-123', JSON.stringify({\n  type: 'message',\n  text: 'Hello!'\n}), true) // Optional: enable compression\n\n// Get the number of subscribers for a topic\nconst count = router.subscriberCount('room-123')\n\n// Upgrade an HTTP request to a WebSocket connection\nrouter.get('/custom-upgrade', (req) =\u003e {\n  const success = router.upgrade(req, {\n    // Optional custom headers for the 101 Switching Protocols response\n    headers: { 'X-Custom-Header': 'value' },\n\n    // Custom data to attach to the WebSocket\n    data: {\n      userId: '123',\n      authenticated: true,\n      permissions: ['read', 'write']\n    }\n  })\n\n  if (!success) {\n    return new Response('Failed to upgrade connection', { status: 400 })\n  }\n\n  // If upgrade is successful, this response is ignored\n  return new Response('Upgraded to WebSocket')\n})\n\n// Get client IP address\nrouter.get('/ip', (req) =\u003e {\n  const ip = router.requestIP(req)\n  return Response.json(ip)\n})\n\n// Set custom timeout for a request\nrouter.get('/long-operation', (req) =\u003e {\n  // Extend timeout to 5 minutes for this specific request\n  router.timeout(req, 300)\n\n  // Perform long operation...\n  return new Response('Operation completed')\n})\n```\n\n### WebSocket Patterns\n\nHere are some common patterns for working with WebSockets:\n\n#### JSON Communication\n\n```typescript\n// Client-side\nconst ws = new WebSocket('ws://localhost:3000/ws')\nws.send(JSON.stringify({ type: 'login', userId: '123' }))\n\n// Server-side\nrouter.websocket({\n  message(ws, message) {\n    try {\n      const data = JSON.parse(message.toString())\n\n      switch (data.type) {\n        case 'login':\n          handleLogin(ws, data.userId)\n          break\n        case 'message':\n          handleMessage(ws, data)\n          break\n      }\n    }\n    catch (e) {\n      ws.send(JSON.stringify({ error: 'Invalid JSON' }))\n    }\n  }\n})\n```\n\n#### Room-Based Chat\n\n```typescript\nrouter.websocket({\n  open(ws) {\n    ws.data = { userId: generateId(), room: 'lobby' }\n    ws.subscribe('lobby')\n    broadcastToRoom('lobby', `${ws.data.userId} joined the lobby`)\n  },\n\n  message(ws, message) {\n    const text = message.toString()\n\n    if (text.startsWith('/join ')) {\n      const newRoom = text.slice(6).trim()\n\n      // Leave current room\n      const oldRoom = ws.data.room\n      ws.unsubscribe(oldRoom)\n      broadcastToRoom(oldRoom, `${ws.data.userId} left the room`)\n\n      // Join new room\n      ws.data.room = newRoom\n      ws.subscribe(newRoom)\n      broadcastToRoom(newRoom, `${ws.data.userId} joined the room`)\n\n      ws.send(`You joined ${newRoom}`)\n    }\n    else {\n      // Regular message\n      broadcastToRoom(ws.data.room, `${ws.data.userId}: ${text}`)\n    }\n  }\n})\n\nfunction broadcastToRoom(room, message) {\n  router.publish(room, message)\n}\n```\n\n#### Handling Backpressure\n\n```typescript\nrouter.websocket({\n  message(ws, message) {\n    // Send a large response\n    const largeData = generateLargeResponse()\n    const result = ws.send(largeData)\n\n    if (result === -1) {\n      // Message was queued due to backpressure\n      console.log('Backpressure detected, will process more in drain event')\n\n      // Store state to resume in drain handler\n      ws.data.pendingOperations = [/_ ...operations to complete _/]\n    }\n  },\n\n  drain(ws) {\n    // Socket is ready to receive more data\n    if (ws.data.pendingOperations?.length) {\n      const nextOp = ws.data.pendingOperations.shift()\n      processOperation(ws, nextOp)\n    }\n  }\n})\n```\n\n## Bun-Native Features\n\nbun-router fully integrates with Bun's native `Bun.serve()` API, leveraging the latest Bun features for optimal performance.\n\n### Static Responses\n\nDefine static routes without handler functions for optimal performance:\n\n```typescript\nrouter.get('/health', () =\u003e new Response('OK'))\nrouter.get('/ready', () =\u003e new Response('Ready', {\n  headers: { 'X-Ready': '1' }\n}))\n\n// These are automatically optimized to Bun's static routes internally\n```\n\n### Method-Specific Handlers\n\nThe router automatically organizes multiple methods for the same path into Bun's method-specific handlers:\n\n```typescript\nrouter.get('/api/posts', getPosts)\nrouter.post('/api/posts', createPost)\nrouter.put('/api/posts/{id}', updatePost)\nrouter.delete('/api/posts/{id}', deletePost)\n\n// These will be organized into a more efficient format:\n// '/api/posts': {\n//   GET: getPosts,\n//   POST: createPost\n// },\n// '/api/posts/{id}': {\n//   PUT: updatePost,\n//   DELETE: deletePost\n// }\n```\n\n### Hot Reloading\n\nUpdate routes without restarting the server:\n\n```typescript\n// Initial setup\nconst router = new Router()\nrouter.get('/api/version', () =\u003e Response.json({ version: '1.0.0' }))\nconst server = await router.serve({ port: 3000 })\n\n// Later, update routes without downtime\nrouter.get('/api/version', () =\u003e Response.json({ version: '2.0.0' }))\nawait router.reload()\n```\n\n### Error Handling\n\nAdd a global error handler for all routes:\n\n```typescript\nrouter.onError((error) =\u003e {\n  console.error(error)\n  return new Response(`Server Error: ${error.message}`, {\n    status: 500,\n    headers: { 'Content-Type': 'text/plain' }\n  })\n})\n\n// Define a route that might throw an error\nrouter.get('/api/risky', () =\u003e {\n  throw new Error('Something went wrong')\n})\n\n// Serve with the error handler\nrouter.serve({ port: 3000 })\n```\n\n### Type-Safe Route Parameters\n\nTypeScript automatically infers parameter types from route paths:\n\n```typescript\nrouter.get('/orgs/{orgId}/repos/{repoId}', (req) =\u003e {\n  // TypeScript knows the shape of req.params\n  const { orgId, repoId } = req.params\n  return Response.json({ orgId, repoId })\n})\n```\n\n### Cookie Handling\n\nBuilt-in support for working with cookies:\n\n```typescript\nrouter.get('/profile', (req) =\u003e {\n  // Read cookies\n  const userId = req.cookies.get('user_id')\n  const theme = req.cookies.get('theme') || 'light'\n\n  return Response.json({ userId, theme })\n})\n\nrouter.get('/login', (req) =\u003e {\n  // Set cookies\n  req.cookies.set('user_id', '12345', {\n    httpOnly: true,\n    secure: true,\n    maxAge: 60 _ 60 * 24 // 1 day\n  })\n\n  return new Response('Logged in')\n})\n\nrouter.get('/logout', (req) =\u003e {\n  // Delete cookies\n  req.cookies.delete('user_id')\n  return new Response('Logged out')\n})\n```\n\n### Authentication Helper\n\nBuilt-in methods for handling various authentication strategies:\n\n```typescript\n// Basic Authentication\nrouter.get('/api/protected', (req) =\u003e {\n  const auth = req.auth.basic()\n\n  if (!auth.isValid()) {\n    return auth.unauthorized('Protected area')\n  }\n\n  const { username, password } = auth.credentials()\n  // Verify against your user database\n\n  return Response.json({ message: 'Authenticated' })\n})\n\n// Bearer Token Authentication\nrouter.get('/api/user-profile', (req) =\u003e {\n  const auth = req.auth.bearer()\n\n  if (!auth.isValid()) {\n    return auth.unauthorized('Invalid token')\n  }\n\n  const token = auth.token()\n  // Verify token validity\n\n  return Response.json({ message: 'Valid token' })\n})\n\n// JWT Authentication\nrouter.post('/api/login', async (req) =\u003e {\n  const { username, password } = await req.json()\n  // Verify credentials\n\n  const auth = req.auth.jwt()\n  const token = auth.sign({ userId: 123, role: 'admin' }, {\n    expiresIn: '1h',\n    secret: 'your-secret-key'\n  })\n\n  return Response.json({ token })\n})\n\nrouter.get('/api/dashboard', (req) =\u003e {\n  const auth = req.auth.jwt()\n\n  if (!auth.verify({ secret: 'your-secret-key' })) {\n    return auth.unauthorized('Invalid JWT')\n  }\n\n  const payload = auth.payload()\n  return Response.json({ user: payload })\n})\n\n// API Key Authentication\nrouter.get('/api/data', (req) =\u003e {\n  const auth = req.auth.apiKey('x-api-key')\n\n  if (!auth.isValid()) {\n    return auth.unauthorized('Invalid API key')\n  }\n\n  const apiKey = auth.key()\n  // Verify API key against database and check scopes\n\n  return Response.json({ data: 'Secure data' })\n})\n\n// OAuth2 Authentication\nrouter.get('/auth/github', (req) =\u003e {\n  const auth = req.auth.oauth2({\n    provider: 'github',\n    clientId: 'your-client-id',\n    redirectUri: 'http://localhost:3000/auth/callback'\n  })\n\n  return auth.redirect()\n})\n\nrouter.get('/auth/callback', async (req) =\u003e {\n  const auth = req.auth.oauth2({\n    provider: 'github',\n    clientId: 'your-client-id',\n    clientSecret: 'your-client-secret',\n    redirectUri: 'http://localhost:3000/auth/callback'\n  })\n\n  const { accessToken, profile } = await auth.handleCallback(req)\n  // Create or update user record\n\n  return Response.redirect('/dashboard')\n})\n\n### File Streaming\n\nEasily stream files with range support:\n\n```typescript\nrouter.get('/files/{filename}', async (req) =\u003e {\n  const filename = req.params.filename\n  const path = `./uploads/${filename}`\n\n  // Simple file streaming\n  return router.streamFile(path, {\n    headers: {\n      'Content-Type': 'application/octet-stream',\n      'Content-Disposition': `attachment; filename=\"${filename}\"`\n    }\n  })\n})\n\n// With range support for video/audio streaming\nrouter.get('/videos/{id}', async (req) =\u003e {\n  const videoPath = `./videos/${req.params.id}.mp4`\n  return router.streamFileWithRanges(videoPath, req)\n})\n```\n\n## Route Groups\n\n```typescript\nrouter.group({\n  prefix: '/api',\n  middleware: [jsonBody()]\n}, () =\u003e {\n  router.get('/users', getUsersHandler)\n  router.post('/users', createUserHandler)\n})\n```\n\n## Parameter Constraints\n\n```typescript\nrouter.get('/users/{id}', getUserHandler)\n  .whereNumber('id')\n\nrouter.get('/categories/{slug}', getCategoryHandler)\n  .whereAlpha('slug')\n\n// Available constraints\nrouter.whereNumber('id')\nrouter.whereAlpha('name')\nrouter.whereAlphaNumeric('username')\nrouter.whereUuid('id')\nrouter.whereIn('status', ['active', 'pending'])\n```\n\n## RESTful Resources\n\n```typescript\n// Creates all RESTful routes for 'posts'\nrouter.resource('posts', 'PostsController')\n\n// Equivalent to:\nrouter.get('/posts', 'PostsController/index')\nrouter.get('/posts/{id}', 'PostsController/show')\nrouter.post('/posts', 'PostsController/store')\nrouter.put('/posts/{id}', 'PostsController/update')\nrouter.delete('/posts/{id}', 'PostsController/destroy')\n```\n\n## Redirects\n\n```typescript\nrouter.redirectRoute('/old-path', '/new-path')\nrouter.permanentRedirectRoute('/very-old-path', '/new-path')\n```\n\n## Domain Routing\n\n```typescript\nrouter.domain('{account}.example.com', () =\u003e {\n  router.get('/', (req) =\u003e {\n    const account = req.params.account\n    return new Response(`Welcome to ${account}'s subdomain!`)\n  })\n})\n```\n\n## Configuration\n\n```typescript\nconst router = new Router({\n  verbose: true,\n  apiPrefix: '/api/v1',\n  defaultMiddleware: {\n    api: ['Middleware/Cors', 'Middleware/JsonBody'],\n    web: ['Middleware/Session', 'Middleware/Csrf']\n  }\n})\n```\n\n## Changelog\n\nPlease see our [releases](https://github.com/stackjs/bun-router/releases) page for more information on what has changed recently.\n\n## Contributing\n\nPlease see [CONTRIBUTING](.github/CONTRIBUTING.md) for details.\n\n## Community\n\nFor help, discussion about best practices, or any other conversation that would benefit from being searchable:\n\n[Discussions on GitHub](https://github.com/stacksjs/bun-router/discussions)\n\nFor casual chit-chat with others using this package:\n\n[Join the Stacks Discord Server](https://discord.gg/stacksjs)\n\n## Postcardware\n\n\"Software that is free, but hopes for a postcard.\" We love receiving postcards from around the world showing where Stacks is being used! We showcase them on our website too.\n\nOur address: Stacks.js, 12665 Village Ln #2306, Playa Vista, CA 90094, United States 🌎\n\n## Sponsors\n\nWe would like to extend our thanks to the following sponsors for funding Stacks development. If you are interested in becoming a sponsor, please reach out to us.\n\n- [JetBrains](https://www.jetbrains.com/)\n- [The Solana Foundation](https://solana.com/)\n\n## License\n\nThe MIT License (MIT). Please see [LICENSE](LICENSE.md) for more information.\n\nMade with 💙\n\n\u003c!-- Badges --\u003e\n[npm-version-src]: https://img.shields.io/npm/v/bun-router?style=flat-square\n[npm-version-href]: https://npmjs.com/package/bun-router\n[github-actions-src]: https://img.shields.io/github/actions/workflow/status/stacksjs/bun-router/ci.yml?style=flat-square\u0026branch=main\n[github-actions-href]: https://github.com/stacksjs/bun-router/actions?query=workflow%3Aci\n\n\u003c!-- [codecov-src]: https://img.shields.io/codecov/c/gh/stacksjs/bun-router/main?style=flat-square\n[codecov-href]: https://codecov.io/gh/stacksjs/bun-router --\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstacksjs%2Fbun-router","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstacksjs%2Fbun-router","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstacksjs%2Fbun-router/lists"}