Skip to content

Domain Routing ​

Domain routing in bun-router allows you to route requests based on the domain or subdomain of the incoming request. This is particularly useful for multi-tenant applications, subdomains for different services, or serving separate content for different domains using the same application.

Basic Domain Routing ​

To define routes for specific domains, use the domain option when defining routes:

typescript
import { Router } from 'bun-router'

const router = new Router()

// Route for the main domain
router.get('/', (req) => {
  return new Response('Welcome to the main site!')
})

// Route for a specific domain
router.get('/', {
  domain: 'admin.example.com'
}, (req) => {
  return new Response('Welcome to the admin panel!')
})

// Route for another domain
router.get('/', {
  domain: 'api.example.com'
}, (req) => {
  return new Response('API documentation')
})

With this configuration:

  • Requests to example.com/ will return "Welcome to the main site!"
  • Requests to admin.example.com/ will return "Welcome to the admin panel!"
  • Requests to api.example.com/ will return "API documentation"

Domain Groups ​

You can group routes under specific domains using the group method with a domain option:

typescript
import { Router } from 'bun-router'

const router = new Router()

// Main domain routes
router.get('/', (req) => {
  return new Response('Main site home page')
})

// Admin domain routes
router.group({
  domain: 'admin.example.com'
}, () => {
  router.get('/', (req) => {
    return new Response('Admin dashboard')
  })

  router.get('/users', (req) => {
    return new Response('User management')
  })

  router.get('/settings', (req) => {
    return new Response('Admin settings')
  })
})

// API domain routes
router.group({
  domain: 'api.example.com'
}, () => {
  router.get('/', (req) => {
    return new Response('API documentation')
  })

  router.get('/v1/users', (req) => {
    return new Response('Users API v1')
  })

  router.get('/v1/products', (req) => {
    return new Response('Products API v1')
  })
})

Subdomain Parameters ​

bun-router allows you to capture dynamic subdomains as parameters, making it possible to create multi-tenant applications:

typescript
import { Router } from 'bun-router'

const router = new Router()

// Main site
router.get('/', (req) => {
  return new Response('Main site')
})

// Tenant subdomains
router.get('/', {
  domain: '{tenant}.example.com'
}, (req) => {
  const { tenant } = req.params
  return new Response(`Welcome to ${tenant}'s dashboard!`)
})

// Specific paths for tenant subdomains
router.get('/settings', {
  domain: '{tenant}.example.com'
}, (req) => {
  const { tenant } = req.params
  return new Response(`Settings for ${tenant}`)
})

With this configuration:

  • Requests to example.com/ will return "Main site"
  • Requests to acme.example.com/ will return "Welcome to acme's dashboard!"
  • Requests to widget.example.com/ will return "Welcome to widget's dashboard!"
  • Requests to acme.example.com/settings will return "Settings for acme"

Domain Parameter Constraints ​

You can add constraints to subdomain parameters to ensure they match specific patterns:

typescript
router.get('/', {
  domain: '{tenant}.example.com'
}, (req) => {
  const { tenant } = req.params
  return new Response(`Welcome to ${tenant}'s dashboard!`)
}).whereAlphaNumeric('tenant')

This will ensure that the tenant parameter can only contain alphanumeric characters.

Domain Wildcards ​

You can use wildcards to match any subdomain:

typescript
// Match any subdomain of example.com
router.get('/', {
  domain: '*.example.com'
}, (req) => {
  // Get the actual subdomain from request
  const host = req.headers.get('host')
  const subdomain = host.split('.')[0]

  return new Response(`Subdomain: ${subdomain}`)
})

Multiple Domains for the Same Route ​

You can specify multiple domains for the same route:

typescript
// Serve the same content for multiple domains
router.get('/', {
  domain: ['example.com', 'example.org', 'example.net']
}, (req) => {
  return new Response('Welcome to our site!')
})

Checking the Domain in Middleware ​

You can create middleware that checks the domain:

typescript
function domainMiddleware(domains) {
  return (req, next) => {
    const host = req.headers.get('host')

    // Remove port if present
    const domain = host.split(':')[0]

    if (domains.includes(domain)) {
      return next(req)
    }

    return new Response('Unauthorized domain', { status: 403 })
  }
}

// Apply domain middleware to a route
router.get('/protected', domainMiddleware(['example.com', 'admin.example.com']), (req) => {
  return new Response('Protected content')
})

Testing Domain Routes ​

When testing domain routes, you need to set the Host header in your requests:

typescript
import { expect, test } from 'bun:test'
import { Router } from 'bun-router'

test('domain routing should work', async () => {
  const router = new Router()

  router.get('/', {
    domain: 'example.com'
  }, (req) => {
    return new Response('Main site')
  })

  router.get('/', {
    domain: 'admin.example.com'
  }, (req) => {
    return new Response('Admin site')
  })

  // Test main domain
  const mainRequest = new Request('http://example.com/', {
    headers: {
      Host: 'example.com'
    }
  })

  const mainResponse = await router.handle(mainRequest)
  expect(await mainResponse.text()).toBe('Main site')

  // Test admin domain
  const adminRequest = new Request('http://admin.example.com/', {
    headers: {
      Host: 'admin.example.com'
    }
  })

  const adminResponse = await router.handle(adminRequest)
  expect(await adminResponse.text()).toBe('Admin site')
})

Practical Examples ​

Multi-tenant SaaS Application ​

typescript
import { Router, session } from 'bun-router'

const router = new Router()

// Main marketing site
router.group({
  domain: 'example.com'
}, () => {
  router.get('/', (req) => {
    return new Response('Welcome to our SaaS app!')
  })

  router.get('/pricing', (req) => {
    return new Response('Our pricing plans')
  })

  router.get('/signup', (req) => {
    return new Response('Sign up for an account')
  })
})

// App routes for tenant subdomains
router.group({
  domain: '{tenant}.example.com',
  middleware: [session(), requireAuth()]
}, () => {
  router.get('/', (req) => {
    const { tenant } = req.params
    return new Response(`${tenant}'s Dashboard`)
  })

  router.get('/projects', (req) => {
    const { tenant } = req.params
    return new Response(`${tenant}'s Projects`)
  })

  router.get('/settings', (req) => {
    const { tenant } = req.params
    return new Response(`${tenant}'s Settings`)
  })
})

// Admin dashboard for managing tenants
router.group({
  domain: 'admin.example.com',
  middleware: [session(), requireAdmin()]
}, () => {
  router.get('/', (req) => {
    return new Response('Admin Dashboard')
  })

  router.get('/tenants', (req) => {
    return new Response('Tenant Management')
  })

  router.get('/billing', (req) => {
    return new Response('Billing Management')
  })
})

Localized Content Based on Country Domains ​

typescript
import { Router } from 'bun-router'

const router = new Router()

// US site
router.group({
  domain: 'example.com'
}, () => {
  router.get('/', (req) => {
    return new Response('US Website - English')
  })

  router.get('/products', (req) => {
    return new Response('US Products with USD pricing')
  })
})

// UK site
router.group({
  domain: 'example.co.uk'
}, () => {
  router.get('/', (req) => {
    return new Response('UK Website - English (UK)')
  })

  router.get('/products', (req) => {
    return new Response('UK Products with GBP pricing')
  })
})

// German site
router.group({
  domain: 'example.de'
}, () => {
  router.get('/', (req) => {
    return new Response('Deutsche Website - Deutsch')
  })

  router.get('/produkte', (req) => {
    return new Response('Deutsche Produkte mit EUR Preisen')
  })
})

Next Steps ​

Now that you understand domain routing in bun-router, check out these related topics:

Released under the MIT License.