@momsfriendlydevco/cowboy
v1.0.19
Published
Wrapper around Cloudflare Wrangler to provide a more Express-like experience
Downloads
22
Keywords
Readme
@MomsfriendlyDevCo/Cowboy
A friendler wrapper around the Cloudflare Wrangler SDK
Features:
- Automatic CORS handling
- Basic router support
- Express-like
req
+res
object for routes - Built in middleware + request validation via Joi
- Built-in debug support for testkits + Wrangler
- Built-in JSON / Multipart (or FormData) / Plain text decoding and population of
req.body
Examples
Simple request output
Example src/worker.js
file providing a GET server which generates random company profiles:
import {faker} from '@faker-js/faker';
import cowboy from '@momsfriendlydevco/cowboy';
export default cowboy()
.use('cors') // Inject CORS functionality in every request
.get('/', ()=> ({
name: faker.company.name(),
motto: faker.company.catchPhrase(),
}))
ReST server example
Example src/worker.js
file providing a GET / POST ReST-like server:
import cowboy from '@momsfriendlydevco/cowboy';
export default cowboy()
.use('cors')
.get('/widgets', ()=> // Fetch a list of widgets
widgetStore.fetchAll()
)
.post('/widgets', async (req, res, env) => { // Create a new widget
let newWidget = await widgetStore.create(req.body);
res.send({id: newWidget.id}); // Explicitly send response
})
.get('/widgets/:id', // Validate params + fetch an existing widget
['validateParams', joi => ({ // Use the 'validateParams' middleware with options
id: joi.number().required().above(10000).below(99999),
})],
req => widgetStore.fetch(req.params.id),
)
.delete('/widgets/:id', // Try to delete a widget
(req, res, env) => { // Apply custom middleware
let isAllowed = await widgetStore.userIsValid(req.headers.auth);
if (!isAllowed) return res.sendStatus(403); // Stop bad actors
},
req => widgetStore.delete(req.params.id)
)
};
Debugging
This module uses the Debug NPM. To enable simply set the DEBUG
environment variable to include cowboy
.
Debugging workers in Testkits will automatically detect this token and enable debugging there. Use the debug
export within Testkits to see output.
API
cowboy()
import cowboy from '@momsfriendlydevco/cowboy';
Instanciate a Cowboy
class instance and provide a simple router skeleton.
Cowboy
import {Cowboy} from '@momsfriendlydevco/cowboy';
The instance created by cowboy()
.
Cowboy.delete(path) / .get() / .head() / .post() / .put() / .options()
Queue up a route with a given path.
Each component is made up of a path + any number of middleware handlers.
let router = new Cowboy()
.get('/my/path', middleware1, middleware2...)
Notes:
- All middleware items are called in sequence - and are async waited-on)
- If any middleware functions fail the entire chain aborts with an error
- All middleware functions are called as
(CowboyRequest, CowboyResponse, Env)
- If any middleware functions call
res.end()
(or any of its automatic methods likeres.send()
/res.sendStatus()
) the chain also aborts successfully - 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
Cowboy.use(middleware)
Queue up a universal middleware handler which will be used on all endpoints.
Middleware is called as per Cowboy.get()
and its equivelents.
Cowboy.resolve(CowboyRequest)
Find the matching route that would be used if given a prototype request.
Cowboy.fetch(CloudflareRequest, CloudflareEnv)
Execute the router when given various Cloudflare inputs.
This function will, in order:
- Enable debugging if required
- Create
(req:CowboyRequest, res:CowboyResponse)
- Execute all middleware setup via
Cowboy.use()
- Find a matching route - if no route is found, raise a 404 and quit
- Execute the matching route middleware, in sequence
- Return the final response - if it the function did not already explicitly do so
CowboyRequest
import CowboyRequest from '@momsfriendlydevco/cowboy/request';
A wrapped version of the incoming CloudflareRequest
object.
This object is identical to the original CloudflareRequest object with the following additions:
| Property | Type | Description |
|------------|----------|----------------------------------------------------------|
| path
| String
| Extracted url.pathname
portion of the incoming request |
| hostname
| String
| Extracted url.hostname
portion of the incoming request |
CowboyResponse
import CowboyResponse from '@momsfriendlydevco/cowboy/request';
An Express-like response object. Calling any method which ends the session will cause the middleware chain to terminate and the response to be served back.
This object contains various Express-like utility functions:
| Method | Description |
|-------------------------------------|----------------------------------------------------------|
| set(options)
| Set response output headers (using an object) |
| set(header, value)
| Alternate method to set headers individually |
| send(data, end=true)
| Set the output response and optionally end the session |
| end(data?, end=true)
| Set the output response and optionally end the session |
| sendStatus(code, data?, end=true)
| Send a HTTP response code and optionally end the session |
| status(code)
| Set the HTTP response code |
| toCloudflareResponse()
| Return the equivelent CloudflareResponse object |
All functions (except toCloudflareResponse()
) are chainable and return the original CowboyResponse
instance.
CowboyTestkit
import CowboyTestkit from '@momsfriendlydevco/cowboy/testkit';
A series of utilities to help write testkits with Wrangler + Cowboy.
CowboyTestkit.cowboyMocha()
Inject various Mocha before/after tooling.
import axios from 'axios';
import {cowboyMocha} from '@momsfriendlydevco/cowboy/testkit';
import {expect} from 'chai';
describe('My Wrangler Endpoint', ()=> {
// Inject Cowboy/mocha testkit handling
cowboyMocha({
axios,
});
let checkCors = headers => {
expect(headers).to.be.an.instanceOf(axios.AxiosHeaders);
expect(headers).to.have.property('access-control-allow-origin', '*');
expect(headers).to.have.property('access-control-allow-methods', 'GET, POST, OPTIONS');
expect(headers).to.have.property('access-control-allow-headers', '*');
expect(headers).to.have.property('content-type', 'application/json;charset=UTF-8');
};
it('should expose CORS headers', ()=>
axios('/', {
method: 'OPTIONS',
}).then(({data, headers}) => {
expect(data).to.be.equal('ok');
checkCors(headers);
})
);
it('should do something useful', ()=>
axios('/', {
method: 'get',
}).then(({data, headers}) => {
checkCors(headers);
// ... Your functionality checks ... //
})
);
});
CowboyTestkit.start(options)
Boot a wranger instance in the background and prepare for testing. Returns a promise.
| Option | Type | Default | Description |
|----------------|------------|---------------|-----------------------------------------------------------|
| axios
| Axios
| | Axios instance to mutate with the base URL, if specified |
| logOutput
| Function
| | Function to wrap STDOUT output. Called as (line:String)
|
| logOutputErr
| Function
| | Function to wrap STDERR output. Called as (line:String)
|
| host
| String
| '127.0.0.1'
| Host to run Wrangler on |
| port
| String
| 8787
| Host to run Wrangler on |
| logLevel
| String
| 'log'
| Log level to instruct Wrangler to run as |
CowboyTestkit.stop()
Terminate any running Wrangler background processes.
Middleware
Cowboy ships with out-of-the-box middleware.
Middleware 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.
To 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.
Middleware can be declared in the following ways:
import cowboy from '@momsfriendlydevco/cowboy';
// Shorthand with defaults - just specify the name
cowboy()
.get('/path',
'cors',
(req, res, env) => /* ... */
)
// Name + options - specify an array with an optional options object
cowboy()
.get('/path',
['cors', {
option1: value1,
/* ... */
}],
(req, res, env) => /* ... */
)
// Middleware function - include the import
import cors from '@momsfriendlydevco/cowboy/middleware/cors';
cowboy()
.get('/path',
cors({
option1: value1,
/* ... */
}),
(req, res, env) => /* ... */
)
cors(options)
Inject simple CORS headers to allow websites to use the endpoint from the browser frontend.
validate(key, validator)
Validate the incoming req.$KEY
object using Joyful.
This function takes two arguments - the req
subkey to examine and the validation function / object.
import cowboy from '@momsfriendlydevco/cowboy';
// Shorthand with defaults - just specify the name
cowboy()
.get('/path',
['validate', 'body', joi => {
widget: joi.string().required().valid('froody', 'doodad'),
size: joi.number().optional(),
})],
(req, res, env) => /* ... */
)
validateBody(validator)
Shorthand validator which runs validation on the req.body
parameter only.
import cowboy from '@momsfriendlydevco/cowboy';
// Shorthand with defaults - just specify the name
cowboy()
.get('/path',
['validateBody', joi => ({
widget: joi.string().required().valid('froody', 'doodad'),
size: joi.number().optional(),
})],
(req, res, env) => /* ... */
)
validateHeaders(validator)
Shorthand validator which runs validation on the req.headers
parameter only.
validateParams(validator)
Shorthand validator which runs validation on the req.params
parameter only.
import cowboy from '@momsfriendlydevco/cowboy';
// Shorthand with defaults - just specify the name
cowboy()
.get('/widgets/:id',
['validateParams', joi => {
id: joi.string().required(),
})],
(req, res, env) => /* ... */
)
validateQuery(validator)
Shorthand validator which runs validation on the req.query
parameter only.
import cowboy from '@momsfriendlydevco/cowboy';
// Shorthand with defaults - just specify the name
cowboy()
.get('/widgets/search',
['validateQuery', joi => {
q: joi.string().requried(),
})],
(req, res, env) => /* ... */
)