# cf-worker-router

> Easier CloudFlare Worker Request Routing

Latest version **0.2.0** (published 2025-08-11) · MIT license · 0 weekly downloads

## Install

```sh
npm install cf-worker-router
pnpm add cf-worker-router
yarn add cf-worker-router
bun add cf-worker-router
```

## Health

**Score 35/100 (D)** — status: maintenance-mode.

Positive: has types; no vulnerabilities; high quality score.

Warnings: low downloads; no esm support; pre 1.0.

Negative: stale; low maintenance score.

## Facts

| | |
|---|---|
| Version | 0.2.0 |
| Published | 2025-08-11 |
| First published | 2019-02-02 |
| Weekly downloads | 0 |
| License | MIT |
| TypeScript types | bundled |
| Module format | CommonJS |
| Dependencies | 0 |
| Unpacked size | 26.9 KB |
| Known vulnerabilities | 0 |
| Install scripts | no |
| GitHub stars | 25 |
| Author | cakedan |
| Maintainers | cakedan |
| Keywords | cloudflare, router, typescript, worker |

## Links

- npm: https://www.npmjs.com/package/cf-worker-router
- Repository: https://github.com/cakedan/cf-worker-router
- Homepage: https://github.com/cakedan/cf-worker-router#readme
- Issues: https://github.com/cakedan/cf-worker-router/issues
- npm.io page: https://npm.io/package/cf-worker-router

## Alternatives

- [express-promise-router](https://npm.io/package/express-promise-router.md) — 736.1K weekly downloads
- [next-usequerystate](https://npm.io/package/next-usequerystate.md) — 29.8K weekly downloads
- [@bitkyc08/opencodex](https://npm.io/package/@bitkyc08/opencodex.md) — 4.6K weekly downloads
- [lynkr](https://npm.io/package/lynkr.md) — 575 weekly downloads
- [baremetal.js](https://npm.io/package/baremetal.js.md) — 42 weekly downloads

## Recent versions

- 0.2.0 (latest) — 2025-08-11
- 0.1.1 — 2019-09-22
- 0.1.0 — 2019-09-21
- 0.0.8 — 2019-03-08
- 0.0.7 — 2019-03-08
- 0.0.6 — 2019-03-08
- 0.0.5 — 2019-02-06
- 0.0.4 — 2019-02-06
- 0.0.3 — 2019-02-03
- 0.0.2 — 2019-02-02
- 0.0.1 — 2019-02-02

## README

# Cloudflare Worker Router
easier cloudflare request routing

## Example Usage
Two ways of adding the router
```js
import { FetchRouter } from 'cf-worker-router';

const router = new FetchRouter();

addEventListener('fetch', (event) => {
  router.onFetch(event);
});
```

or

```ts
import { FetchRouter } from 'cf-worker-router';

const router = new FetchRouter();

// this is the only way to get the environmental variables found in event.environment
// this is also the only way to get access to R2 storage, event.environment.R2_BUCKET
export default {
  fetch(request: Request, env: Record<string, any>, context: ExecutionContext) {
    return router.onFetch(request, env, context);
  }
}
```

```js
import { ApiError, ApiRedirect, DomainRouter, FetchRouter } from 'cf-worker-router';

const router = new FetchRouter();

// export the cloudflare event listener
export default {
  fetch(request, env, context) {
    return router.onFetch(request, env, context);
  }
}

// after every response, modify it (like setting CORS headers)
// is optional
router.beforeResponse = (response, event) => {
  // create a new Response instance, incase it's immutable like from a fetch request
  response = new Response(response.body, response);
  response.headers.set('access-control-allow-headers', 'Content-Type, X-Some-Header');
  response.headers.set('access-control-allow-methods', '*');
  response.headers.set('access-control-allow-origin', event.url.origin || '*');
  return response;
};


// same as .route(url, 'GET', handler);
// GET */users/1234
router.route('/users/:userId', async (event) => {
  // automatically converts anything not of Response type to ApiResponse
  return event.parameters;
});

// same as .route(url, ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'], handler)
// ANY-METHOD */proxy/:url
router.route('/proxy/:url', '*', async (event) => {
  if (event.request.headers.get('secret-token') !== 'test') {
    return new ApiError({status: 403});
  }
  // remove our ip from headers
  event.request.headers.delete('cf-connecting-ip');
  event.request.headers.delete('x-real-ip');
  return await fetch(event.parameters.url, event.request);
});

// GET redirect.example.com/:url
router.route('redirect.example.com/:url', async (event) => {
  return new ApiRedirect(event.parameters.url);
});

// GET example.com/string-test/anystringhere
// GET example.com/string-test/anystringhere/andanythingwithslashes
router.route('example.com/string-test/:string...', async (event) => {
  return event.parameters;
});

// GET example.club/pass
// passes it onto original destination, doesn't call `event.respondWith()`
router.route('example.com/pass', {pass: true});


const subDomain = new DomainRouter(':username.example.com');
router.addRouter(subDomain);

// GET some-username.example.com/files/1234
subDomain.get('/files/:fileId', async (event) => {
  // {username, fileId} are the parameters
  return event.parameters;
});

```

## Example Usage w/ Minified Version
```js
// Copy and Paste ./min/router.min.js at the top of the file
// !function(e){...

// We put our library in self.CFWorkerRouter
const { ApiError, ApiRedirect, DomainRouter, FetchRouter } = CFWorkerRouter;

const router = new FetchRouter();

// add the cloudflare event listener
export default {
  fetch(request, env, context) {
    return router.onFetch(request, env, context);
  }
}

// after every response, modify it (like setting CORS headers)
// is optional
router.beforeResponse = (response, event) => {
  // create a new Response instance, incase it's immutable like from a fetch request
  response = new Response(response.body, response);
  response.headers.set('access-control-allow-headers', 'Content-Type, X-Some-Header');
  response.headers.set('access-control-allow-methods', '*');
  response.headers.set('access-control-allow-origin', event.url.origin || '*');
  return response;
};


// same as .route(url, 'GET', handler);
// GET */users/1234
router.route('/users/:userId', async (event) => {
  // automatically converts anything not of Response type to ApiResponse
  return event.parameters;
});

// same as .route(url, ['GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'], handler)
// ANY-METHOD */proxy/:url
router.route('/proxy/:url', '*', async (event) => {
  if (event.request.headers.get('secret-token') !== 'test') {
    return new ApiError({status: 403});
  }
  // remove our ip from headers
  event.request.headers.delete('cf-connecting-ip');
  event.request.headers.delete('x-real-ip');
  return await fetch(event.parameters.url, event.request);
});

// GET redirect.example.com/:url
router.route('redirect.example.com/:url', async (event) => {
  return new ApiRedirect(event.parameters.url);
});

// GET example.com/string-test/anystringhere
// GET example.com/string-test/anystringhere/andanythingwithslashes
router.route('example.com/string-test/:string...', async (event) => {
  return event.parameters;
});

// GET example.club/pass
// passes it onto original destination, doesn't call `event.respondWith()`
router.route('example.com/pass', {pass: true});


const subDomain = new DomainRouter(':username.example.com');
router.addRouter(subDomain);

// GET some-username.example.com/files/1234
subDomain.get('/files/:fileId', async (event) => {
  // {username, fileId} are the parameters
  return event.parameters;
});

```

---
_Source: https://npm.io/package/cf-worker-router · Machine-readable twin of the npm.io package page. Health data is recomputed on every publish._
