Security News
Bun 1.2 Released with 90% Node.js Compatibility and Built-in S3 Object Support
Bun 1.2 enhances its JavaScript runtime with 90% Node.js compatibility, built-in S3 and Postgres support, HTML Imports, and faster, cloud-first performance.
** Quick Reference links: **
Bagpipes was developed as a way to enable API flows and mashups to be created declaratively in YAML without writing code. It works a lot like functional programming... there's no global state, data just gets passed from one function to the next down the line until we're done.
For example, to expose an API to get the latitude and longitude of an address using Google's Geocode API, one could simply define this flow:
google_geocode:
name: http # system fitting (type is optional)
input:
url: http://maps.googleapis.com/maps/api/geocode/json?sensor=true
params:
address: .request.parameters.address.value[0]
getAddressLocation:
- google_geocode # call the fitting defined in this swagger
- path: body # system fitting: get body from output
- parse: json # body is a json string, parse to object
- path: results # get results from body
- first # get first result
- path: geometry.location # output = { lat: n, lng: n }
But that's just a quick example, you can do much, much more... including filtering, error handling, and even parallel handling like mashup HTTP requests to multiple services.
Here's a very simple "Hello, World" example:
var bagpipes = require('bagpipes');
var pipesDef = {
MyPipe: [
{ emit: 'Hello, World!' }
]
};
var pipesConfig = {};
var pipes = bagpipes.create(pipesDef, pipesConfig);
var pipe = pipes.getPipe('MyPipe');
var context = {};
pipes.play(pipe, context);
console.log(context.output);
That said, you'll likely load your pipe definitions from a file something like this:
var yaml = require('js-yaml');
var pipesDefs = yaml.safeLoad(fs.readFileSync('whatever.yaml'));
Have fun!
So what are these things called "fittings"? Well, simply, if a pipe is a list of steps, a fitting describes what a step actually accomplishes.
Let's take a very simple example: Say we have some data that looks like this:
[ { "name": "Scott", "city": "Los Angeles" }
{ "name": "Jeff", "city": "San Francisco" } ]
Now, we'll create a pipe that just retrieves the first name. In the definition below, we've defined a pipe called "getFirstUserName" that consists of a couple of system-provided fittings:
getFirstUserName:
- first
- path: name
The "first" fitting selects the first element of an array passed in. The "path" fitting selects the "user" attribute from the object passed on by the first fitting. Thus, the result from our example is "Scott".
Or, say we want to get all the names from our data as an array. We could simply do it like this:
getUserNames:
- pick: name
Obviously, these are trivial examples, but you can create pipes as long and as complex as you wish. In fact, you can even write your own fittings... but we're getting ahead of ourselves.
When you want to use a fitting, you have 2 options:
Let's look at the 2nd type. Here's an example of a fitting that calls out to an API with a URL that looks like something like this: http://maps.googleapis.com/maps/api/geocode/json?sensor=true?address=Los%20Angeles. And, of course, we'll want to make the address dynamic. This requires a a little bit of configuration: We need to tell the "http" fitting the URL, the operation, and what parameters to use (and how to get them):
geocode:
name: http
input:
operation: get
url: http://maps.googleapis.com/maps/api/geocode/json
params:
sensor: true
address: .output.address[0]
As you can see above, we've give our fitting a name ("geocode") and specified which type of fitting we're creating (a "system" fitting called "http"). This fitting requires several inputs including the HTTP operation, the URL, and parameters to pass. Each of these is just a static string in this case except for the "address" parameter. The address is merely retrieved by picking the "address" property from the "output" object of whatever fitting came before it in the pipe. (Note: There are several options for input sources that will be defined later.)
By default, the output of this operation will be placed on the pipe in the "output" variable for the next fitting to use - or to be returned to the client if it's the last fitting to execute.
A Pipe is just defined in YAML as an Array. It can be reference by its key and can reference other pipes and fittings by their keys. Each step in a pipe may be one of the following:
If a fitting reference includes a value, that value will be emitted onto the output for the fitting to consume. Most of the system fittings are able to operate solely on the output without any additional configuration - similar to a Unix pipe.
Generally, a pipe flows from top to bottom in serial manner. However, in some cases it is desirable to execute two pipes in parallel (for example, a mashup of two external APIs).
Parallel execution of pipes can be done by using key/value pairs on the pipe in place of a single step. The output from each pipe will be assigned to the key associated with it. It's probably easiest to explain by example:
getRestaurantsAndWeather:
- getAddressLocation
- restaurants: getRestaurants
weather: getWeather
This pipe will first flow through getAddressLocation step. Then, because the restaurants and weather keys are both on the same step, it will execute the getRestaurants and getWeather pipes concurrently. The final output of this pipe will be an object that looks like this: { restaurants: {...}, weather: {...} } where the values will be the output from the respective pipes.
The context object that is passed through the pipe has the following properties that should be generally used by the fittings to accept input and deliver output via the pipe to other fittings or to the client:
In addition, the context has the following properties that should not be modified - and, in general, you shouldn't need to access them at all:
Finally, the context object itself will contain any properties that you've assigned to it via the 'output' option on your fitting definition.
Notes:
The context object is extensible as well. The names listed above as well as any name starting with '_' should be considered reserved, but you may assign other additional properties to the context should you need it for communication between fittings (see also the memo fitting).
You may install a custom error handler pipe by specifying them using the system onError fitting.
All fittings may have the following values (all of which are optional):
If type is omitted (as it must be for in-line usage), Bagpipes will first check the user fittings then the system fittings for the name and use the first fitting found. Thus be aware that if you define a fitting with the same name as a system one, your fitting will override it.
The input may be a hash, array, or constant. The value or sub-values of the input is defined as either:
A reference is a value populated either from data on the request or from the output of previous fittings on the pipe. It is defined like so:
key: # the variable name (key) on context.input to which the value is assigned
path: '' # the variable to pick from context using [json path syntax](https://www.npmjs.com/package/jspath)
default: '' # (optional) value to assign if the referenced value is undefined
Note: If there is no input definition, input will be assigned to the prior fitting's output.
See also Context for more information.
There are 2 basic types of system fittings: Internal fittings that just modify output in a flow and those that are callouts to other systems. These are listed below by category:
Amend the pipe output by copying the fields from input. Overrides output. Input and output must be objects.
Emit the fitting's input onto onto the pipe output.
Used for testing and will likely be removed, but evaluates provided javascript directly.
Select the first element from an array.
Selects output using json path syntax.
Saves the current context.output value to context[key]. Can be later retrieved via:
emit:
name: key
in: context
Omit the specified key or keys from an object.
In case of error, redirect the flow to the specified pipe.
Run multiple pipe flows concurrently. Generally not used directly (use shorthand syntax on pipe).
Parses a String. Currently input must be 'json'.
Selects an element from an object by dot-delimited keys.
Selects only the specified key or keys from an object.
Render the object using a mustache template specified as the string or loaded from the filename in the user view directory.
Select the values of an object as an array.
Make a call to a URL.
config keys:
input keys:
output:
{ status: statusCode headers: JSON string body: JSON string }
The user fitting is a custom function you can write and place in the fittings directory. It requires the following values:
exampleUserFitting:
name: customizeResponse
Javascript implementation:
A user fitting is a fitting defined in the user's fittings directory. It exposes a creation function that accepts a fittingDefinition and the swagger-pipes configuration. This function is executed during parsing. Thus, it should access the fittingDef.config (if any) and create any static resources at this time.
The creation function returns an execution function that will called during pipe flows. This function accepts a context object and a standard javascript asynchronous callback. When executed, this function should perform its intended function and then call the callback function with (error, response) when complete. Here's an example that will query Yelp for businesses near a location with an input of { latitude: n, longitude: n }:
var Yelp = require('yelp');
var util = require('util');
module.exports = function create(fittingDef, bagpipes) {
var yelp = Yelp.createClient(fittingDef.config);
return function yelp_search(context, cb) {
var input = context.input;
var options = {
term: input.term,
ll: util.format('%s,%s', input.latitude, input.longitude)
};
yelp.search(options, function(error, data) {
if (error) { return cb(error); }
if (data.error) { return cb(data.error); }
cb(null, data.businesses);
});
}
};
You can access Swagger APIs by simply loading that Swagger. A Swagger fitting expects this:
exampleSwaggerFitting:
type: swagger
url: http://petstore.swagger.io/v2/swagger.json
A node-machine is a self-documenting component format that we've adapted to the a127 (see http://node-machine.org). You can use a node-machine just by using 'npm install' and declaring the fitting. The fitting definition expects a minimum of:
exampleNodeMachineFitting:
type: node-machine
machinepack: machinepack-github
machine: list-repos
Controller fittings merely provide a call to one of the controllers you've defined in your /controllers directory for use with swagger-tools router. However, given that these controllers probably interact directly with the response and aren't designed for use within the Bagpipes system, proceed with extreme caution.
exampleControllerFitting:
type: controller
controller: my_module
function: someFunction
Currently, debugging is limited to reading log entries and the debugger. However, there is a lot of information available to you by enabling the DEBUG log. By enabling the DEBUG=pipes log, you will be able to see the entire flow of the swagger-pipes system sent to the console:
DEBUG=pipes
You can get more debug information from the fittings with:
DEBUG=pipes:fittings
You can also emit the actual output from each step by enabling pipes:content:
DEBUG=pipes:content
Finally, you can enable all the pipes debugging by using a wildcard:
DEBUG=pipes*
Enjoy!
FAQs
Less code, more flow. Let's dance!
The npm package bagpipes receives a total of 4,702 weekly downloads. As such, bagpipes popularity was classified as popular.
We found that bagpipes demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Bun 1.2 enhances its JavaScript runtime with 90% Node.js compatibility, built-in S3 and Postgres support, HTML Imports, and faster, cloud-first performance.
Security News
Biden's executive order pushes for AI-driven cybersecurity, software supply chain transparency, and stronger protections for federal and open source systems.
Security News
Fluent Assertions is facing backlash after dropping the Apache license for a commercial model, leaving users blindsided and questioning contributor rights.