Skip to content

Wrapper around Cloudflare Wrangler to provide a more Express-like experience

License

Notifications You must be signed in to change notification settings

MomsFriendlyDevCo/Cowboy

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

74 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

@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)
	)
};


Cron schedule handling
----------------------
Cron scheduling is a little basic at the moment but likely to improve in the future.
To set up a Cron handler simply install it by calling `.schedule(callback)`:

```javascript
import cowboy from '@momsfriendlydevco/cowboy';

export default cowboy()
	.schedule(async (event, env, ctx) => {
		// Handle cron code here
	})

Debugging
---------
This module uses the [Debug NPM](https://github.com/visionmedia/debug#readme). 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()
--------
```javascript
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 like res.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:

  1. Enable debugging if required
  2. Create (req:CowboyRequest, res:CowboyResponse)
  3. Execute all middleware setup via Cowboy.use()
  4. Find a matching route - if no route is found, raise a 404 and quit
  5. Execute the matching route middleware, in sequence
  6. Return the final response - if it the function did not already explicitly do so

Cowboy.proxy(path, request, env)

Forward from one route to another as if the second route was called first.

Cowboy.schedule(callback)

Install a scheduled Cron handler function.

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) => /* ... */
	)

parseJwt()

Parse the incoming request as a JWT string and decode its contents into req.body.

About

Wrapper around Cloudflare Wrangler to provide a more Express-like experience

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published