• Stars
    star
    1,446
  • Rank 31,242 (Top 0.7 %)
  • Language
  • Created over 9 years ago
  • Updated over 5 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

AngularJS style guide used at GoCardless

Angular Style Guide

Table of Contents

  1. High-level Goals
  2. Third-party Dependencies
  3. Directory and File Structure
  4. Parts of Angular
  5. Testing
  6. General Patterns and Anti-patterns

High-level Goals

The principles we use to guide low-level decision making are:

  1. Prioritise readability.
  2. Be explicit, not implicit.
  3. Favour composability over inheritance.
  4. Think forward โ€“ ES6 and Web Components (Angular 2.0).
  5. Know when to deviate from the style guide.

Example ES6 Application Scaffold

ES6 + AngularJS Application

Third-party Dependencies

  1. SystemJS
  • Why: SystemJS is an ES6 module loader that enables us to load assets in development and transpile ES6 to ES5 in production.
 import {dialogControllerModule} from './dialog.controller';
 import template from './dialog.template.html!text';
  1. Traceur
  • Why: Traceur is a transpiler supported by SystemJS. It lets us use ECMAScript 6 features before they are implemented in browsers.
  1. Lo-Dash
  • Why: lodash is a utility library we use throughout our application. Our use of _.extend could be replaced by Angularโ€™s built in method angular.extend.
  1. AngularUI Router
  • Why: ui-router replaces Angularโ€™s ngRoute module, and is built around states instead of URL routes, enabling nested views. Our use of $stateProvider could be replaced by $routeProvider.

Note: For a complete setup see our Example ES6 Application Scaffold.

Directory and File Structure

We organise our code as follows:

Folder structure

/app
  /components
    /alert
      alert.directive.js
      alert.directive.spec.js
      alert.template.html
  /config
    main.config.js
  /constants
    api-url.constant.js
  /routes
    /customers
      /index
        customers-index.template.html
        customers-index.route.js
        customers-index.controller.js
        customers-index.e2e.js
  /helpers
    /currency
      currency-filter.js
      currency-filter.spec.js
    /unit
    /e2e
  /services
    /creditors
      creditors.js
      creditors.spec.js
  bootstrap.js
  main.js
/assets
  /fonts
  /images
  /stylesheets
404.html
index.html

Specs (Unit/E2E)

Keep spec files in the same folder as the code being tested.

Components

Components are encapsulated DOM components. Each component contains all the HTML, CSS, JavaScript, and other dependencies needed to render itself.

Routes

A view, made up of components and unique pieces of UI, that points to a URL. Like components, each route contains all the HTML, CSS, JavaScript, and other dependencies needed to render itself.

Services

Services contain Business logic. For example, $http abstractions.

Config

Configures Providers. For example, $locationProvider.html5Mode(true);.

Constants

Although JavaScript does not yet support constants, we run our application through Traceur, which supports const.

The constant should be named in all uppercase if it's a global constant that will be used across many different functions. For example, export const API_URL = 'https://api.gocardless.com'.

If the constant is defined within a single function, it should be in regular camelCase.

Helpers

Pure functions. For example, a currencyFilter might take in a number and format it for a certain currency. Helpers take input and return output without having any side effects.

Parts of Angular

Rules for using each of the core parts of AngularJS (routes, directives, controllers, modules, and templates).

Routes

Use resolvers to inject data.

Why: The page is rendered only when all data is available. This means views are only rendered once all the required data is available, and you avoid the user seeing any empty views whilst the data is loading.

// Recommended
$stateProvider.state('customers.show', {
  url: '/customers/:id',
  template: template,
  controller: 'CustomersShowController',
  controllerAs: 'ctrl',
  resolve: {
    customer: [
      'Customers',
      '$stateParams',
      function customerResolver(Customers, $stateParams) {
        return Customers.findOne({
          params: { id: $stateParams.id }
        });
      }
    ]
  }
});

// Avoid
// Note: Controller written inline for the example
$stateProvider.state('customers.show', {
  url: '/customers/:id',
  template: template,
  controllerAs: 'ctrl',
  controller: [
    'Customers',
    '$stateParams',
    function CustomersShowController(Customers, $stateParams) {
      const ctrl = this;
      Customers.findOne({
        params: { id: $stateParams.id }
      }).then(function(customers) {
        ctrl.customers = customers;
      });
    }
  ]
});

Use query parameters to store route state. For example, the current offset and limit when paginating.

Why: The current view should be accurately reflected in the URL, which means any page refresh puts the user back in the exact state they were in.

// Recommended
function nextPage() {
  const currentOffset = parseInt($stateParams.offset, 10) || 0;
  const limit = parseInt($stateParams.limit, 10) || 10;
  const nextOffset = currentOffset + limit;
  Payments.findAll({
    params: { customers: $stateParams.id, limit: limit, offset: nextOffset }
  });
}

// Avoid
// Keeping route state in memory only
let currentOffset = 0;
const limit = 10;

function nextPage() {
  const nextOffset = currentOffset + limit;
  currentOffset = nextOffset;
  Payments.findAll({
    params: { customers: $stateParams.id, limit: limit, offset: nextOffset }
  });
}

Directives

Directive names must only contain a-z and at least one dash (-).

Why: Custom elements must have a dash (namespace) to differentiate them from native elements and prevent future component collisions.

<!-- Recommended -->
<dialog-box></dialog-box>
<button click-toggle="isActive"></button>

<!-- Avoid -->
<dialog></dialog>
<button toggle="isActive"></button>

Use element directives when content is injected, else use attribute directives.

Why: Separates responsibility: element directives add content; attribute directives add behaviour; class attributes add style.

<!-- Recommended -->
<alert-box message="Error"></alert-box>
<!-- Replaced with: -->
<alert-box message="Error" class="ng-isolate-scope">
  <div class="alert-box">
    <span class="alert-box__message">Error</span>
  </div>
</alert-box>

<!-- Avoid -->
<p alert-box message="Error"></p>
<!-- Replaced with: -->
<p alert-box message="Error">
  <div class="alert-box">
    <span class="alert-box__message">Error</span>
  </div>
</p>
<!-- Recommended -->
<button prevent-default="click">Submit</button>

<!-- Avoid -->
<prevent-default event="click">Submit</prevent-default>

Use an isolate scope for element directives. Share scope for attribute directives.

Why: Using an isolate scope forces you to expose an API by giving the component all the data it needs. This increases reusability and testability. When using a shared scope for attribute directives you should not write to it or rely on any existing data. Attribute directives should not have an isolate scope because doing so overwrites the current scope.

// Recommended
angular.module('alertListComponentModule', [])
  .directive('alertList', [
    function alertListDirective() {
      return {
        restrict: 'E',
        scope: {}
      };
    }
  ]);

// Avoid
angular.module('alertListComponentModule', [])
  .directive('alertList', [
    function alertListDirective() {
      return {
        restrict: 'E'
      };
    }
  ]);
// Recommended
angular.module('alertListComponentModule', [])
  .directive('alertList', [
    function alertListDirective() {
      return {
        restrict: 'A'
      };
    }
  ]);

// Avoid
angular.module('alertListComponentModule', [])
  .directive('alertList', [
    function alertListDirective() {
      return {
        restrict: 'A',
        scope: {}
      };
    }
  ]);

angular.module('alertListComponentModule', [])
.directive('alertList', [
  function alertListDirective() {
    return {
      restrict: 'A',
      scope: true
    };
  }
]);

When using isolate-scope properties, always bindToController.

Why: It explicitly shows what variables are shared via the controller.

// Recommended
angular.module('alertListComponentModule', [])
  .directive('alertList', [
    function alertListDirective() {
      return {
        restrict: 'E',
        controller: 'AlertListController',
        controllerAs: 'ctrl',
        bindToController: true,
        template: template,
        scope: {}
      };
    }
  ]);

// Avoid
angular.module('alertListComponentModule', [])
  .directive('alertList', [
    function alertListDirective() {
      return {
        restrict: 'E',
        controller: 'AlertListController',
        template: template,
        scope: {}
      };
    }
  ]);

Tear down directives, subscribe to $scope.$on('$destroy', ...) to get rid of any event listeners or DOM nodes created outside the directive element.

Why: It avoids memory leaks and duplicate event listeners being bound when the directive is re-created.

// Recommended
angular.module('adminExpandComponentModule', [])
  .directive('adminExpand', [
    '$window',
    function adminExpand($window) {
      return {
        restrict: 'A',
        scope: {},
        link: function adminExpandLink(scope, element) {
          function expand() {
            element.addClass('is-expanded');
          }

          $window.document.addEventListener('click', expand);

          scope.$on('$destroy', function onAdminExpandDestroy() {
            $window.document.removeEventListener('click', expand);
          });
        }
      };
    }
  ]);

// Avoid
angular.module('adminExpandComponentModule', [])
  .directive('adminExpand', [
    '$window',
    function adminExpand($window) {
      return {
        restrict: 'A',
        scope: {},
        link: function adminExpandLink(scope, element) {
          function expand() {
            element.addClass('is-expanded');
          }

          $window.document.addEventListener('click', expand);
        }
      };
    }
  ]);

Anti-Patterns

  • Don't rely on jQuery selectors. Use directives to target elements instead.
  • Don't use jQuery to generate templates or DOM. Use directive templates instead.
  • Don't prefix directive names with x-, polymer-, ng-.

Controllers

Use controllerAs syntax.

Why: It explicitly shows what controller a variable belongs to, by writing {{ ctrl.foo }} instead of {{ foo }}.

// Recommended
$stateProvider.state('authRequired.customers.show', {
  url: '/customers/:id',
  template: template,
  controller: 'CustomersShowController',
  controllerAs: 'ctrl'
});

// Avoid
$stateProvider.state('authRequired.customers.show', {
  url: '/customers/:id',
  template: template,
  controller: 'CustomersShowController'
});
// Recommended
angular.module('alertListComponentModule', [])
  .directive('alertList', [
    function alertListDirective() {
      return {
        restrict: 'E',
        controller: 'AlertListController',
        controllerAs: 'ctrl',
        bindToController: true,
        template: template,
        scope: {}
      };
    }
  ]);

// Avoid
angular.module('alertListComponentModule', [])
  .directive('alertList', [
    function alertListDirective() {
      return {
        restrict: 'E',
        controller: 'AlertListController',
        template: template,
        scope: {}
      };
    }
  ]);

Inject ready data instead of loading it in the controller.

Why:

  • 2.1. Simplifies testing with mock data.
  • 2.2. Separates concerns: data is resolved in the route and used in the controller.
// Recommended
angular.module('customersShowControllerModule', [])
  .controller('CustomersShowController', [
    'customer', 'payments', 'mandates',
    function CustomersShowController(customer, payments, mandates){
      const ctrl = this;

      _.extend(ctrl, {
        customer: customer,
        payments: payments,
        mandates: mandates
      });
    }
  ]);

// Avoid
angular.module('customersShowControllerModule', [])
  .controller('CustomersShowController', [
    'Customers', 'Payments', 'Mandates', '$stateParams',
    function CustomersShowController(Customers, Payments, Mandates, $stateParams){
      const ctrl = this;

      Customers.findOne({
        params: { id: $stateParams.id }
      }).then(function(customers) {
        ctrl.customers = customers;
      });

      Payments.findAll().then(function(payments) {
        ctrl.payments = payments;
      });

      Mandates.findAll().then(function(mandates) {
        ctrl.mandates = mandates;
      });
    }
  ]);

Extend a controllerโ€™s properties onto the controller.

Why: What is being exported is clear and always done in one place, at the bottom of the file.

// Recommended
angular.module('organisationRolesNewControllerModule', [])
  .controller('OrganisationRolesNewController', [
    'permissions',
    function CustomersShowController(permissions){
      const ctrl = this;

      function setAllPermissions(access) {
        ctrl.form.permissions.forEach(function(permission) {
          permission.access = access;
        });
      }

      _.extend(ctrl, {
        permissions: permissions,
        setAllPermissions: setAllPermissions
      });
    }
  ]);

// Avoid
angular.module('organisationRolesNewControllerModule', [])
  .controller('OrganisationRolesNewController', [
    'permissions',
    function CustomersShowController(permissions){
      const ctrl = this;

      ctrl.permissions = permissions;

      ctrl.setAllPermissions = function setAllPermissions(access) {
        ctrl.form.permissions.forEach(function(permission) {
          permission.access = access;
        });
      }
    }
  ]);

Only extend the controller with properties used in templates.

Why: Adding unused properties to the digest cycle is expensive.

// Recommended
angular.module('webhooksIndexControllerModule', [])
  .controller('WebhooksIndexController', [
    'TestWebhooks', 'AlertList', 'webhooks'
    function WebhooksIndexController(TestWebhooks, AlertList, webhooks) {
      const ctrl = this;

      function success() {
        AlertList.success('Your test webhook has been created and will be sent shortly');
      }

      function error() {
        AlertList.error('Failed to send test webhook, please try again');
      }

      function sendTestWebhook(webhook) {
        TestWebhooks.create({
          data: { test_webhooks: webhook }
        }).then(success, error);
      }

      _.extend(ctrl, {
        webhooks: webhooks,
        sendTestWebhook: sendTestWebhook
      });
    }
  ]);

// Avoid
angular.module('webhooksIndexControllerModule', [])
  .controller('WebhooksIndexController', [
    'TestWebhooks', 'AlertList', 'webhooks'
    function WebhooksIndexController(TestWebhooks, AlertList, webhooks) {
      const ctrl = this;

      function success() {
        AlertList.success('Your test webhook has been created and will be sent shortly');
      }

      function error() {
        AlertList.error('Failed to send test webhook, please try again');
      }

      function sendTestWebhook(webhook) {
        TestWebhooks.create({
          data: { test_webhooks: webhook }
        }).then(success, error);
      }

      _.extend(ctrl, {
        webhooks: webhooks,
        success: success,
        error: error,
        sendTestWebhook: sendTestWebhook
      });
    }
  ]);

Store presentation logic in controllers and business logic in services.

Why:

  • 5.1. Simplifies testing business logic.
  • 5.2. Controllers are glue code, and therefore require integration tests, not unit tests.
// Recommended
angular.module('webhooksControllerModule', [])
.controller('WebhooksController', [
  'TestWebhooks',
  function WebhooksController(TestWebhooks) {
    const ctrl = this;

    function sendTestWebhook(webhook) {
      TestWebhooks.create({
        data: { test_webhooks: webhook }
      }).then(function() {
        $state.go('authRequired.organisation.roles.index', null);
        AlertList.success('Your test webhook has been created and will be sent shortly');
      });
    }

    _.extend(ctrl, {
      sendTestWebhook: sendTestWebhook
    });
  }
]);

// Avoid
angular.module('webhooksControllerModule', [])
.controller('WebhooksController', [
  '$http',
  function WebhooksController($http) {
    const ctrl = this;

    function sendTestWebhook(webhook) {
      $http({
        method: 'POST',
        data: { test_webhooks: webhook },
        url: '/test_webhooks'
      });
    }

    _.extend(ctrl, {
      sendTestWebhook: sendTestWebhook
    });
  }
]);

Only instantiate controllers through routes or directives.

Why: Allows reuse of controllers and encourages component encapsulation.

// Recommended
angular.module('alertListComponentModule', [])
  .directive('alertList', [
    function alertListDirective() {
      return {
        restrict: 'E',
        controller: 'AlertListController',
        controllerAs: 'ctrl',
        bindToController: true,
        template: template,
        scope: {}
      };
    }
  ]);
<!-- Avoid -->
<div ng-controller='AlertListController as ctrl'>
  <span>{{ ctrl.message }}</span>
</div>

Anti-Patterns

  • Donโ€™t manipulate DOM in your controllers, this will make them harder to test. Use directives instead.

Services and Factories

Treat Service objects like a Class with static methods; don't export services as a single function

Why: easier to see at the definition site and the call site exactly what function the service provides.

// Recommend
angular.module('buildCSVModule', []).factory('BuildCSV', function buildCVS() {
  function build() {
    // ...
  }

  return {
    build: build,
  }
});

// Avoid
angular.module('buildCSVModule', []).factory('BuildCSV', function buildCSV() {
  function build() {
    // ...
  }

  return build;
});

Services that take in data and manipulate it should never mutate the original object and return the new object

Why: Avoiding mutation in service objects makes it possible to reason about them from their call site without knowing what they do internally.

// Recommend
let events = [...];
events = EventPresenterService.present(events);

// Avoid
const events = [...];
EventPresenterService.present(events);
// events has been mutated

Prefer ImmutableJS when creating services that manipulate data

Use fromJS to convert a JS object into an Immutable type, and toJS at the end to convert back.

Why: ImmutableJS is a fantastic library for taking data and manipulating it without ever mutating.

// Recommend
angular.module('eventPresenterModule', []).factory('EventPresenterService', function eventPresenterService() {
  function present(events) {
    return Immutable.fromJS(events).map(function(event) {
      event.set('some_data', true);
    }).map(function(event) {
      ...
    }).toJS();
  }

  return {
    present: present,
  }
});

Modules

Name a module using lowerCamelCase and append Module.

Why: A module name should be mapped to a file and clearly differentiated from constructors and service objects.

// Recommended
angular.module('usersPasswordEditControllerModule', [])
  .controller('UsersPasswordEditController', []);

// Avoid
angular.module('UsersPasswordEditControllerModule', [])
  .controller('UsersPasswordEditController', []);

Create one module per file and donโ€™t alter a module other than where it is defined.

Why:

  • 1.1. Prevents polluting the global scope.
  • 1.2. Simplifies unit testing by declaring all dependencies needed to run each module.
  • 1.3. Negates necessity to load files in a specific order.
// Recommended
angular.module('usersPasswordEditControllerModule', [])
  .controller('UsersPasswordEditController', []);

// Avoid
angular.module('app')
  .controller('UsersPasswordEditController', []);

Use ES6 module system and reference other modules using Angular Moduleโ€™s name property.

Why:

  • 2.1. Encapsulates all required files, making unit testing easier and error feedback more specific.
  • 2.2. Simplifies upgrading to Angular 2.0, which uses ES6 modules.
// Recommended
import {passwordResetTokensModule} from 'app/services/password-reset-tokens/password-reset-tokens';
import {sessionModule} from 'app/services/session/session';
import {alertListModule} from 'app/components/alert-list/alert-list';

export const usersPasswordEditControllerModule = angular.module('usersPasswordEditControllerModule', [
  passwordResetTokensModule.name,
  sessionModule.name,
  alertListModule.name
]);

// Avoid
import {passwordResetTokensModule} from 'app/services/password-reset-tokens/password-reset-tokens';
import {sessionModule} from 'app/services/session/session';
import {alertListModule} from 'app/components/alert-list/alert-list';

export const usersPasswordEditControllerModule = angular.module('usersPasswordEditControllerModule', [
  'passwordResetTokensModule',
  'sessionModule',
  'alertListModule'
]);

Use relative imports only when importing from the current directory or any of its children. Use absolute paths when referencing modules in parent directories.

Why: Makes it easier to edit directories.

// Current directory: app/services/creditors/

// Recommended
import {API_URL} from 'app/constants/api-url.constant';
import {authInterceptorModule} from 'app/services/auth-interceptor/auth-interceptor';
import {organisationIdInterceptorModule} from 'app/services/organisation-id-interceptor/organisation-id-interceptor';

// Avoid
import {API_URL} from '../../constants/api-url.constant';
import {authInterceptorModule} from '../services/auth-interceptor/auth-interceptor';
import {organisationIdInterceptorModule} from '../services/organisation-id-interceptor/organisation-id-interceptor';

Templates

Use the one-time binding syntax when data does not change after first render.

Why: Avoids unnecessary expensive $watchers.

<!-- Recommended -->
<p>Name: {{::ctrl.name}}</p>

<!-- Avoid -->
<p>Name: {{ctrl.name}}</p>

Anti-Patterns

  • Donโ€™t use ngInit โ€“ use controllers instead.
  • Donโ€™t use <div ng-controller="Controller"> syntax. Use directives instead.

Testing

Our applications are covered by two different types of test:

  • unit tests, which test individual components by asserting that they behave as expected.
  • End to End, or E2E, tests, which load up the application in a browser and interact with it as if a user would, asserting the application behaves expectedly.

To write our tests we use Jasmine BDD and ngMock.

Unit Testing

Every component should have a comprehensive set of unit tests.

Structure of Unit Tests

Tests should be grouped into logical blocks using Jasmine's describe function. Tests for a function should all be contained within a describe block, and describe blocks should also be used to describe different scenarios, or contexts:

describe('#update', function() {
  describe('when the data is valid', function() {
    it('shows the success message', function() {โ€ฆ});
  });

  describe('when the data is invalid', function() {
    it('shows errors', function() {โ€ฆ});
  });
});

Dependencies

Each component should have its dependencies stubbed in each test.

Inject the dependencies and the components being tested in a beforeEach function. This encapsulates each test's state, ensuring that they are independent, making them easier to reason about. Tests should never depend on being run in a specific order.

let SomeService;

beforeEach(inject(function($injector) {
  SomeService = $injector.get('SomeService');
}));

Controllers

When injecting controllers for a test, use the controller as syntax:

beforeEach(inject(function($injector, $controller) {
  $controller('OrganisationController as ctrl', {โ€ฆ});
}));

Always create a new scope to pass into the controller:

let scope;
let organisation = {
  name: 'GoCardless'
};

beforeEach(inject(function($injector, $controller) {
  scope = $injector.get('$rootScope').$new();
  $controller('OrganisationController as ctrl', {
    $scope: scope,
    organisation: organisation
  });
}));

Fixtures

When stubbing an API request using $httpBackend, always respond with a correctly formatted object. These responses should be saved individually as .json files and imported using the SystemJS JSON plugin:

import updateFixture from 'app/services/roles/update.fixture.json!json';

$httpBackend.expectPUT('someurl.com').respond(201, updateFixture);

General Patterns and Anti-Patterns

Rules that pertain to our application at large, not a specific part of Angular.

Patterns

Angular abstractions

Use:

  • $timeout instead of setTimeout
  • $interval instead of setInterval
  • $window instead of window
  • $document instead of document
  • $http instead of $.ajax
  • $q (promises) instead of callbacks

Why: This makes tests easier to follow and faster to run as they can be executed synchronously.

Dependency injection annotations

Always use array annotation for dependency injection and bootstrap with strictDi.

Why: Negates the need for additional tooling to guard against minification and strictDi throws an error if the array (or $inject) syntax is not used.

// Recommended
angular.module('creditorsShowControllerModule', [])
  .controller('CreditorsShowController', [
    'creditor', 'payments', 'payouts',
    function CreditorsShowController(creditor, payments, payouts) {
      const ctrl = this;

      _.extend(ctrl, {
        creditor: creditor,
        payments: payments,
        payouts: payouts
      });
    }
  ]);

// Avoid
angular.module('creditorsShowControllerModule', [])
  .controller('CreditorsShowController',
    function CreditorsShowController(creditor, payments, payouts) {
      const ctrl = this;

      _.extend(ctrl, {
        creditor: creditor,
        payments: payments,
        payouts: payouts
      });
    });
// Recommended
import {mainModule} from './main';

angular.element(document).ready(function() {
  angular.bootstrap(document.querySelector('[data-main-app]'), [
    mainModule.name
  ], {
    strictDi: true
  });
});

// Avoid
import {mainModule} from './main';

angular.element(document).ready(function() {
  angular.bootstrap(document.querySelector('[data-main-app]'), [
    mainModule.name
  ]);
});

Anti-patterns

Donโ€™t use the $ name space in property names (e.g. $scope.$isActive = true).

Why: Makes clear what is an Angular internal.

Don't use globals. Resolve all dependencies using Dependency Injection.

Why: Using DI makes testing and refactoring easier.

Don't do if (!$scope.$$phase) $scope.$apply(), it means your $scope.$apply() isn't high enough in the call stack.

Why: You should $scope.$apply() as close to the asynchronous event binding as possible.

Credits

We referred to lots of resources during the creation of this styleguide, including:

More Repositories

1

statesman

A statesmanlike state machine library.
Ruby
1,700
star
2

business

Ruby business day calculations
Ruby
494
star
3

http-api-design

HTTP Design Guidelines
413
star
4

airflow-dbt

Apache Airflow integration for dbt
Python
367
star
5

es6-angularjs

JavaScript
180
star
6

coach

Alternative controllers with middleware
Ruby
164
star
7

logjam

a log shipping tool
Go
134
star
8

our-postgresql-setup

PostgreSQL clustering with corosync/pacemaker test environment
Shell
126
star
9

nandi

Fear free PostgreSQL migrations for Rails
Ruby
123
star
10

activerecord-safer_migrations

Safer ActiveRecord migrations for Postgres
Ruby
116
star
11

amqpc

AMQP CLI tool
Go
115
star
12

pgreplay-go

Postgres load testing tool
Go
112
star
13

rspec-activejob

RSpec matchers for testing ActiveJob
Ruby
98
star
14

ibandit

Convert national banking details into IBANs, and vice-versa.
Ruby
95
star
15

gocardless-pro-php

GoCardless Pro PHP Client
PHP
89
star
16

gocardless-legacy-php

The PHP client library for the GoCardless Legacy API
PHP
66
star
17

stolon-pgbouncer

Add-on to stolon for providing zero-downtime failover and PgBouncer integration
Go
59
star
18

gocardless-legacy-ruby

The Ruby client library for the GoCardless API
Ruby
52
star
19

gc-http-factory

A factory for creating $http services in Angular.
JavaScript
44
star
20

draupnir

Anonymised database instances as-a-service
Go
41
star
21

bump

Automated dependency management for Ruby, Python and Javascript
Ruby
35
star
22

gocardless-pro-python

GoCardless Pro Python Client
Python
33
star
23

stubby

Your favourite pretender stubber
JavaScript
29
star
24

gocardless-pro-ruby

GoCardless Pro Ruby Client
Ruby
27
star
25

gocardless-dotnet

GoCardless .NET Client
C#
25
star
26

utopia-getting-started

Sharing a copy of our getting-started tutorial, as a demonstration of how our infrastructure works with utopia
24
star
27

resque-sentry

A Resque failure backend that sends errors to Sentry
Ruby
23
star
28

statesman-events

Event support for Statesman (UNMAINTAINED)
Ruby
23
star
29

prius

Environmentally-friendly application config
Ruby
22
star
30

systemjs-assetgraph

AssetGraph transform for optimizing SystemJS pages for production
JavaScript
22
star
31

gocardless-legacy-python

The Python client library for the GoCardless API
Python
22
star
32

theatre

GoCardless' collection of Kubernetes extensions
Go
20
star
33

gocardless-nodejs

GoCardless Node.js client
TypeScript
19
star
34

javascript-style-guide

The GoCardless JavaScript styleguide
18
star
35

pgsql-cluster-manager

Daemon and migration tool that manages Postgres cluster using etcd/corosync/pacemaker
Go
17
star
36

anony

A small library that defines how ActiveRecord models should be anonymised for deletion purposes.
Ruby
17
star
37

drydock

DryDock is a utility to clean up Docker images
Go
16
star
38

gocardless-pro-java

GoCardless Pro Java Client
Java
15
star
39

ng-gc-components

JavaScript
14
star
40

slo-builder

Templates for building SLOs with Prometheus rules and alerts
Go
14
star
41

legacy-api-docs

Docs for GoCardless legacy API
JavaScript
14
star
42

business-python

Python business day calculations
Python
13
star
43

gocardless-legacy-dotnet

The .NET client library for the GoCardless Legacy API
C#
12
star
44

companies-house-rest

Ruby wrapper for the Companies House REST API.
Ruby
12
star
45

gocardless-legacy-node

The Node.js client library for the GoCardless Legacy API
JavaScript
11
star
46

html-style-guide

How we write HTML at GoCardless
11
star
47

bump-core

The core logic powering Bump
Ruby
11
star
48

atum

Ruby HTTP client generator for APIs represented with JSON schema
Ruby
9
star
49

gocardless-legacy-java

The Java client library for the GoCardless Legacy API
Java
9
star
50

codeigniter-gocardless

The CodeIgniter spark for the GoCardless API
PHP
8
star
51

logsearch

Search Logstash / Elasticsearch logs from the command line
Go
7
star
52

sample-legacy-django-app

A sample Django app demonstrating the use of the GoCardless Legacy API and the Python client.
Python
7
star
53

companies-house-gateway-ruby

Ruby wrapper for the Companies House XML Gateway
Ruby
6
star
54

airflow-looker

A collection of Airflow extensions to provide integration with Looker
Python
6
star
55

uk_phone_numbers

A Ruby library for validating and formatting UK phone numbers.
Ruby
6
star
56

callcredit-ruby

Ruby wrapper for Callcredit's CallValidate API
Ruby
6
star
57

sample-legacy-rails-app

A sample Rails app demonstrating the use of GoCardless Legacy API and the Ruby client.
Ruby
6
star
58

react-dropin

React bindings for the GoCardless Dropin checkout flow
TypeScript
5
star
59

creditsafe-ruby

Ruby library for the Creditsafe SOAP API
Ruby
5
star
60

github-archive

Easy way to archive an entire organisation repos on S3
Go
5
star
61

gocardless-pro-go

Go
5
star
62

simple-swag

Dead simple swagger/openapi docs server
Go
3
star
63

bank-webfont

A webfont of prominent UK banks
CSS
3
star
64

cli-releases

Release repo for the gocardless cli
Dockerfile
3
star
65

gc_ruboconfig

GoCardless Engineering shared rubocop config
Ruby
3
star
66

gocardless-pro-ruby-example

Example of using the GoCardless Pro Ruby client library
HTML
3
star
67

gocardless-legacy-example-django

An example site using the GoCardless Legacy API
Python
3
star
68

belongs-to-one-of

Gem to support activemodel relations where one model can be a child of one of many models
Ruby
3
star
69

coach-demo

Ruby
3
star
70

gocardless-php

Placeholder explaining our PHP API libraries.
2
star
71

prometheus-client-ruby-data-stores-experiments

Ruby
2
star
72

que

A Ruby job queue that uses PostgreSQL's advisory locks for speed and reliability.
Ruby
2
star
73

slackify

Update your Slack status with what you're listening to in Spotify
Elixir
2
star
74

slackify-dot-rb

Take 2: this time without a language barrier
Ruby
1
star
75

gocardless-pro-java-maven-example

An example java app that handles webhooks
Java
1
star
76

open-charities

A Ruby library for querying the OpenCharities database
Ruby
1
star
77

bucket-store

Helper library to access cloud storage services
Ruby
1
star
78

publish-techdocs-action

Action to generate and publish TechDocs
Shell
1
star
79

passfort

Ruby client library for the PassFort API
Ruby
1
star
80

salesforce_wrapper

A wrapper around Restforce, catching exceptions and performing a configurable action with them (e.g. sending an email).
Ruby
1
star
81

gocardless-pro-java-example

Example of using the GoCardless Pro Java client library
Java
1
star
82

homebrew-taps

Ruby
1
star
83

gocardless-legacy-partner-example-ruby

An example GoCardless partner app, written in Sinatra
Ruby
1
star
84

gocardless-pro-php-demo

Pro client PHP demo
PHP
1
star
85

rspec-que

RSpec matchers for testing Que
Ruby
1
star