{"id":22428483,"url":"https://github.com/momsfriendlydevco/cowboy","last_synced_at":"2025-08-01T10:32:44.326Z","repository":{"id":196926789,"uuid":"697513690","full_name":"MomsFriendlyDevCo/Cowboy","owner":"MomsFriendlyDevCo","description":"Wrapper around Cloudflare Wrangler to provide a more Express-like experience","archived":false,"fork":false,"pushed_at":"2024-11-27T06:03:05.000Z","size":399,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2024-11-27T07:19:00.369Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","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/MomsFriendlyDevCo.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}},"created_at":"2023-09-27T22:18:05.000Z","updated_at":"2024-11-27T06:03:09.000Z","dependencies_parsed_at":null,"dependency_job_id":"fa59b484-6278-4c67-9e28-2905d5b56186","html_url":"https://github.com/MomsFriendlyDevCo/Cowboy","commit_stats":null,"previous_names":["momsfriendlydevco/cowboy"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MomsFriendlyDevCo%2FCowboy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MomsFriendlyDevCo%2FCowboy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MomsFriendlyDevCo%2FCowboy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MomsFriendlyDevCo%2FCowboy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MomsFriendlyDevCo","download_url":"https://codeload.github.com/MomsFriendlyDevCo/Cowboy/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":228363963,"owners_count":17908319,"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":[],"created_at":"2024-12-05T20:14:56.563Z","updated_at":"2025-08-01T10:32:44.208Z","avatar_url":"https://github.com/MomsFriendlyDevCo.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"@MomsfriendlyDevCo/Cowboy\n=========================\nA friendler wrapper around the Cloudflare [Wrangler SDK](https://github.com/cloudflare/workers-sdk)\n\nFeatures:\n\n* Automatic CORS handling\n* Basic router support\n* Express-like `req` + `res` object for routes\n* Built in middleware + request validation via [Joi](https://joi.dev)\n* Built-in debug support for testkits + Wrangler\n* Built-in JSON / Multipart (or FormData) / Plain text decoding and population of `req.body`\n\n\nExamples\n========\n\nSimple request output\n---------------------\nExample `src/worker.js` file providing a GET server which generates random company profiles:\n\n```javascript\nimport {faker} from '@faker-js/faker';\nimport cowboy from '@momsfriendlydevco/cowboy';\n\nexport default cowboy()\n\t.use('cors') // Inject CORS functionality in every request\n\t.get('/', ()=\u003e ({\n\t\tname: faker.company.name(),\n\t\tmotto: faker.company.catchPhrase(),\n\t}))\n```\n\n\n\nReST server example\n-------------------\nExample `src/worker.js` file providing a GET / POST ReST-like server:\n\n```javascript\nimport cowboy from '@momsfriendlydevco/cowboy';\n\nexport default cowboy()\n\t.use('cors')\n\t.get('/widgets', ()=\u003e // Fetch a list of widgets\n\t\twidgetStore.fetchAll()\n\t)\n\t.post('/widgets', async (req, res, env) =\u003e { // Create a new widget\n\t\tlet newWidget = await widgetStore.create(req.body);\n\t\tres.send({id: newWidget.id}); // Explicitly send response\n\t})\n\t.get('/widgets/:id', // Validate params + fetch an existing widget\n\t\t['validateParams', joi =\u003e ({ // Use the 'validateParams' middleware with options\n\t\t\tid: joi.number().required().above(10000).below(99999),\n\t\t})],\n\t\treq =\u003e widgetStore.fetch(req.params.id),\n\t)\n\t.delete('/widgets/:id', // Try to delete a widget\n\t\t(req, res, env) =\u003e { // Apply custom middleware\n\t\t\tlet isAllowed = await widgetStore.userIsValid(req.headers.auth);\n\t\t\tif (!isAllowed) return res.sendStatus(403); // Stop bad actors\n\t\t},\n\t\treq =\u003e widgetStore.delete(req.params.id)\n\t)\n};\n\n\nCron schedule handling\n----------------------\nCron scheduling is a little basic at the moment but likely to improve in the future.\nTo set up a Cron handler simply install it by calling `.schedule(callback)`:\n\n```javascript\nimport cowboy from '@momsfriendlydevco/cowboy';\n\nexport default cowboy()\n\t.schedule(async (event, env, ctx) =\u003e {\n\t\t// Handle cron code here\n\t})\n```\n```\n\nDebugging\n---------\nThis module uses the [Debug NPM](https://github.com/visionmedia/debug#readme). To enable simply set the `DEBUG` environment variable to include `cowboy`.\n\nDebugging workers in Testkits will automatically detect this token and enable debugging there. Use the `debug` export within Testkits to see output.\n\n\n\nAPI\n===\n\ncowboy()\n--------\n```javascript\nimport cowboy from '@momsfriendlydevco/cowboy';\n```\nInstanciate a `Cowboy` class instance and provide a simple router skeleton.\n\n\nCowboy\n------\n```javascript\nimport {Cowboy} from '@momsfriendlydevco/cowboy';\n```\nThe instance created by `cowboy()`.\n\n\nCowboy.delete(path) / .get() / .head() / .post() / .put() / .options()\n----------------------------------------------------------------------\nQueue up a route with a given path.\n\nEach component is made up of a path + any number of middleware handlers.\n\n```javascript\nlet router = new Cowboy()\n\t.get('/my/path', middleware1, middleware2...)\n```\n\nNotes:\n* All middleware items are called in sequence - and are async waited-on)\n* If any middleware functions fail the entire chain aborts with an error\n* All middleware functions are called as `(CowboyRequest, CowboyResponse, Env)`\n* If any middleware functions call `res.end()` (or any of its automatic methods like `res.send()` / `res.sendStatus()`) the chain also aborts successfully\n* If the last middleware function returns a non response object - i.e. the function didn't call `res.send()` its assumed to be a valid output and is automatically wrapped\n\n\nCowboy.use(middleware)\n----------------------\nQueue up a universal middleware handler which will be used on *all* endpoints.\nMiddleware is called as per `Cowboy.get()` and its equivelents.\n\n\nCowboy.resolve(CowboyRequest)\n-----------------------------\nFind the matching route that would be used if given a prototype request.\n\n\nCowboy.fetch(CloudflareRequest, CloudflareEnv)\n----------------------------------------------\nExecute the router when given various Cloudflare inputs.\n\nThis function will, in order:\n\n1. Enable debugging if required\n2. Create `(req:CowboyRequest, res:CowboyResponse)`\n3. Execute all middleware setup via `Cowboy.use()`\n4. Find a matching route - if no route is found, raise a 404 and quit\n5. Execute the matching route middleware, in sequence\n6. Return the final response - if it the function did not already explicitly do so\n\n\nCowboy.proxy(path, request, env)\n--------------------------------\nForward from one route to another as if the second route was called first.\n\n\nCowboy.schedule(callback)\n-------------------------\nInstall a scheduled Cron handler function.\n\n\nCowboyRequest\n-------------\n```javascript\nimport CowboyRequest from '@momsfriendlydevco/cowboy/request';\n```\nA wrapped version of the incoming `CloudflareRequest` object.\n\nThis object is identical to the original [CloudflareRequest](https://developers.cloudflare.com/workers/runtime-apis/request/#properties) object with the following additions:\n\n| Property   | Type     | Description                                              |\n|------------|----------|----------------------------------------------------------|\n| `path`     | `String` | Extracted `url.pathname` portion of the incoming request |\n| `hostname` | `String` | Extracted `url.hostname` portion of the incoming request |\n\n\nCowboyResponse\n--------------\n```javascript\nimport CowboyResponse from '@momsfriendlydevco/cowboy/request';\n```\nAn Express-like response object.\nCalling any method which ends the session will cause the middleware chain to terminate and the response to be served back.\n\nThis object contains various Express-like utility functions:\n\n| Method                              | Description                                              |\n|-------------------------------------|----------------------------------------------------------|\n| `set(options)`                      | Set response output headers (using an object)            |\n| `set(header, value)`                | Alternate method to set headers individually             |\n| `send(data, end=true)`              | Set the output response and optionally end the session   |\n| `end(data?, end=true)`              | Set the output response and optionally end the session   |\n| `sendStatus(code, data?, end=true)` | Send a HTTP response code and optionally end the session |\n| `status(code)`                      | Set the HTTP response code                               |\n| `toCloudflareResponse()`            | Return the equivelent CloudflareResponse object          |\n\nAll functions (except `toCloudflareResponse()`) are chainable and return the original `CowboyResponse` instance.\n\n\nCowboyTestkit\n-------------\n```javascript\nimport CowboyTestkit from '@momsfriendlydevco/cowboy/testkit';\n```\nA series of utilities to help write testkits with Wrangler + Cowboy.\n\n\nCowboyTestkit.cowboyMocha()\n---------------------------\nInject various Mocha before/after tooling.\n\n```javascript\nimport axios from 'axios';\nimport {cowboyMocha} from '@momsfriendlydevco/cowboy/testkit';\nimport {expect} from 'chai';\n\ndescribe('My Wrangler Endpoint', ()=\u003e {\n\n\t// Inject Cowboy/mocha testkit handling\n\tcowboyMocha({\n\t\taxios,\n\t});\n\n\tlet checkCors = headers =\u003e {\n\t\texpect(headers).to.be.an.instanceOf(axios.AxiosHeaders);\n\t\texpect(headers).to.have.property('access-control-allow-origin', '*');\n\t\texpect(headers).to.have.property('access-control-allow-methods', 'GET, POST, OPTIONS');\n\t\texpect(headers).to.have.property('access-control-allow-headers', '*');\n\t\texpect(headers).to.have.property('content-type', 'application/json;charset=UTF-8');\n\t};\n\n\tit('should expose CORS headers', ()=\u003e\n\t\taxios('/', {\n\t\t\tmethod: 'OPTIONS',\n\t\t}).then(({data, headers}) =\u003e {\n\t\t\texpect(data).to.be.equal('ok');\n\t\t\tcheckCors(headers);\n\t\t})\n\t);\n\n\tit('should do something useful', ()=\u003e\n\t\taxios('/', {\n\t\t\tmethod: 'get',\n\t\t}).then(({data, headers}) =\u003e {\n\t\t\tcheckCors(headers);\n\n\t\t\t// ... Your functionality checks ... //\n\t\t})\n\t);\n\n});\n```\n\n\nCowboyTestkit.start(options)\n----------------------------\nBoot a wranger instance in the background and prepare for testing.\nReturns a promise.\n\n| Option         | Type       | Default       | Description                                               |\n|----------------|------------|---------------|-----------------------------------------------------------|\n| `axios`        | `Axios`    |               | Axios instance to mutate with the base URL, if specified  |\n| `logOutput`    | `Function` |               | Function to wrap STDOUT output. Called as `(line:String)` |\n| `logOutputErr` | `Function` |               | Function to wrap STDERR output. Called as `(line:String)` |\n| `host`         | `String`   | `'127.0.0.1'` | Host to run Wrangler on                                   |\n| `port`         | `String`   | `8787`        | Host to run Wrangler on                                   |\n| `logLevel`     | `String`   | `'log'`       | Log level to instruct Wrangler to run as                  |\n\n\nCowboyTestkit.stop()\n--------------------\nTerminate any running Wrangler background processes.\n\n\nMiddleware\n==========\nCowboy ships with out-of-the-box middleware.\nMiddleware are simple functions which accept the paramters `(req:CowboyRequest, res:CowboyResponse)` and can modify the request, halt output with a call to `res` or perform other Async actions before continuing to the next middleware item.\n\nTo use middleware in your routes you can either declare it using `.use(middleware)` - which installs it globally or `.ROUTE(middleware...)` which installs it only for that route.\n\nMiddleware can be declared in the following ways:\n\n```javascript\nimport cowboy from '@momsfriendlydevco/cowboy';\n\n// Shorthand with defaults - just specify the name\ncowboy()\n\t.get('/path',\n\t\t'cors',\n\t\t(req, res, env) =\u003e /* ... */\n\t)\n\n// Name + options - specify an array with an optional options object\ncowboy()\n\t.get('/path',\n\t\t['cors', {\n\t\t\toption1: value1,\n\t\t\t/* ... */\n\t\t}],\n\t\t(req, res, env) =\u003e /* ... */\n\t)\n\n\n// Middleware function - include the import\nimport cors from '@momsfriendlydevco/cowboy/middleware/cors';\ncowboy()\n\t.get('/path',\n\t\tcors({\n\t\t\toption1: value1,\n\t\t\t/* ... */\n\t\t}),\n\t\t(req, res, env) =\u003e /* ... */\n\t)\n```\n\n\ncors(options)\n-------------\nInject simple CORS headers to allow websites to use the endpoint from the browser frontend.\n\n\ndevOnly()\n---------\nAllow access to the endpoint ONLY if Cloudflare is running in local development mode. Throws a 403 otherwise.\n\n\nvalidate(key, validator)\n------------------------\nValidate the incoming `req.$KEY` object using [Joyful](https://github.com/MomsFriendlyDevCo/Joyful).\nThis function takes two arguments - the `req` subkey to examine and the validation function / object.\n\n```javascript\nimport cowboy from '@momsfriendlydevco/cowboy';\n\n// Shorthand with defaults - just specify the name\ncowboy()\n\t.get('/path',\n\t\t['validate', 'body', joi =\u003e {\n\t\t\twidget: joi.string().required().valid('froody', 'doodad'),\n\t\t\tsize: joi.number().optional(),\n\t\t})],\n\t\t(req, res, env) =\u003e /* ... */\n\t)\n```\n\nvalidateBody(validator)\n-----------------------\nShorthand validator which runs validation on the `req.body` parameter only.\n\n\n```javascript\nimport cowboy from '@momsfriendlydevco/cowboy';\n\n// Shorthand with defaults - just specify the name\ncowboy()\n\t.get('/path',\n\t\t['validateBody', joi =\u003e ({\n\t\t\twidget: joi.string().required().valid('froody', 'doodad'),\n\t\t\tsize: joi.number().optional(),\n\t\t})],\n\t\t(req, res, env) =\u003e /* ... */\n\t)\n```\n\nvalidateHeaders(validator)\n-----------------------\nShorthand validator which runs validation on the `req.headers` parameter only.\n\n\nvalidateParams(validator)\n-------------------------\nShorthand validator which runs validation on the `req.params` parameter only.\n\n\n```javascript\nimport cowboy from '@momsfriendlydevco/cowboy';\n\n// Shorthand with defaults - just specify the name\ncowboy()\n\t.get('/widgets/:id',\n\t\t['validateParams', joi =\u003e {\n\t\t\tid: joi.string().required(),\n\t\t})],\n\t\t(req, res, env) =\u003e /* ... */\n\t)\n```\n\n\nvalidateQuery(validator)\n------------------------\nShorthand validator which runs validation on the `req.query` parameter only.\n\n\n```javascript\nimport cowboy from '@momsfriendlydevco/cowboy';\n\n// Shorthand with defaults - just specify the name\ncowboy()\n\t.get('/widgets/search',\n\t\t['validateQuery', joi =\u003e {\n\t\t\tq: joi.string().requried(),\n\t\t})],\n\t\t(req, res, env) =\u003e /* ... */\n\t)\n```\n\n\nparseJwt()\n----------\nParse the incoming request as a JWT string and decode its contents into `req.body`.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmomsfriendlydevco%2Fcowboy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmomsfriendlydevco%2Fcowboy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmomsfriendlydevco%2Fcowboy/lists"}