Skip to content

Latest commit

 

History

History

extensions

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 
 
 
 
 
 
 

Extensions

It is possible to extend the GraphBrainz schema to add integrations with third-party services that provide more information about MusicBrainz entities. Extensions can define new GraphQL types and use the extend type syntax to add new fields to any existing GraphBrainz type, including the root query.

Several extensions are included by default, and you can install any number of additional extensions from a package manager or write your own.

Contents

Loading Extensions

The extensions to load are specified using the extensions option to the exported middleware() function. Each extension must be an object conforming to the Extension API, or the path to a module to load via require() that exports such an object.

If you are running GraphBrainz as a standalone server, you may specify extensions via the GRAPHBRAINZ_EXTENSIONS environment variable, which will be parsed as a JSON array. For example:

$ export GRAPHBRAINZ_EXTENSIONS='["graphbrainz/extensions/fanart-tv"]'
$ graphbrainz

Note that some extensions may require additional configuration via extra options or environment variables. Check the documentation for each extension you use.

The default extensions configuration looks like this:

middleware({
  extensions: [
    'graphbrainz/extensions/cover-art-archive',
    'graphbrainz/extensions/fanart-tv',
    'graphbrainz/extensions/mediawiki',
    'graphbrainz/extensions/the-audio-db',
  ],
});

Built-in Extensions

The following extensions are included with GraphBrainz and loaded by default. See their respective documentation pages for schema info and config options.

  • Cover Art Archive: Retrieve cover art images for releases from the Cover Art Archive.
  • fanart.tv: Retrieve high quality artwork for artists, releases, and labels from fanart.tv.
  • MediaWiki: Retrieve information from MediaWiki image pages, like the actual image file URL and EXIF metadata.
  • TheAudioDB: Retrieve images and information about artists, releases, and recordings from TheAudioDB.com.

More Extensions

The following extensions are published separately, but can easily be added to GraphBrainz by installing them:

  • Last.fm: Retrieve artist, release, and recording information from Last.fm.
  • Discogs: Retrieve artist, label, release, and release group information from Discogs.
  • Spotify: Retrieve artist, release, and recording information from Spotify.

Extension API

The core idea behind extensions comes from the schema stitching feature from graphql-tools, although GraphBrainz does not currently use the exact technique documented there. Instead, we call parse and extendSchema from GraphQL.js, followed by addResolversToSchema.

Extensions must export an object shaped like so:

type Extension = {
  name: string,
  description?: string,
  extendContext?: (context: Context, options: Options) => Context,
  extendSchema?:
    | { schemas: Array<string | DocumentNode>, resolvers: ResolverMap }
    | ((schema: GraphQLSchema, options: Options) => GraphQLSchema),
};

Properties

name

The name of the extension.

description

A description of the functionality that the extension provides.

extendContext

An optional function that accepts a base context object (the context argument available to resolver functions) and returns a new context object. Extensions that access third-party APIs should add any API client instances they need here. The recommended way is to create a loader with dataloader and add it onto context.loaders.

extendSchema

An optional object or function to extend the GraphBrainz schema.

If it is an object, it should have a schemas array and a resolvers object. Each schema must be a string (containing type definitions in GraphQL schema language) or a DocumentNode (if the type definitions have already been parsed). The resolvers object should contain a mapping of type fields to new resolver functions for those fields. See addResolversToSchema.

If it is a function, it should accept schema and options arguments and return a new schema. Use this if you’d like to perform custom schema extension logic. This may be necessary if you already have a GraphQLSchema instance and want to use mergeSchemas, for example. In most cases, you should keep it simple and use the object form.

Example

module.exports = {
  name: 'Hello World',
  description: 'A simple example extension.',
  extendSchema: {
    schemas: [
      `
      extend type Query {
        helloWorld: String!
      }
    `,
    ],
    resolvers: {
      Query: {
        helloWorld: {
          resolve: () => 'It worked!',
        },
      },
    },
  },
};

This will allow the following query to be made:

{
  helloWorld
}

See the code for the built-in extensions for more examples.

Extension Guidelines

Extensions can load and resolve data in any manner they please, and you can write them in any way that conforms to the API. But if you want an extra feather in your cap, there are a few guidelines you should follow in order to maintain consistency with GraphBrainz and the built-in extensions. Here are some tips for writing a good extension:

  • If you need to make HTTP requests, using a Client subclass will get you rate limiting, error handling, retrying, and a Promise-based API for free.
  • Default to following the rate limiting rules of any APIs you use. If there are no guidelines on rate limiting, consider playing nice anyway and limiting your client to around 1 to 10 requests per second.
  • Use a DataLoader instance to batch and cache requests. Even if the data source doesn’t support batching, DataLoader will help by deduping in-flight requests for the same key, preventing unnecessary requests.
  • Use a configurable cache and make sure you aren’t caching everything indefinitely by accident. The cacheMap option to DataLoader is a good place to put it.
  • Get as much configuration from environment variables as possible so that users can just run the standalone server instead of writing any code. If you need more complex configuration, use a single field on the options object as a namespace for your extension’s options.
  • Don’t hesitate to rename fields returned by third-party APIs when translating them to the GraphQL schema. Consistency with GraphQL conventions and the GraphBrainz schema is more desirable than consistency with the original API being used. Some general rules:
    • Match type names to the service they’re coming from (e.g. many services use the words “album” and “track” and the type names should reflect that), but match scalar field names to their MusicBrainz equivalents when possible (e.g. name for artists but title for releases and recordings).
    • Use camel case naming and capitalize acronyms (unless they are the only word), e.g. id, url, artistID, pageURL.
    • If it’s ambiguous whether a field refers to an object/list vs. a scalar summary of an object/list, consider clarifying the field name, e.g. useruserID, membersmemberCount.
    • Don’t include fields that are already available in MusicBrainz (unless it’s possible to retrieve an entity that isn’t in MusicBrainz). Only include what’s relevant and useful.
  • Add descriptions for everything: types, fields, arguments, enum values, etc. – with Markdown links wherever they’d be helpful.
  • When extending the built-in types, prefer adding a single object field that serves as a namespace rather than adding many fields. That way it’s more obvious that the data source isn’t MusicBrainz itself, and you’re less likely to conflict with new MusicBrainz fields in the future.
  • Prefer using a Relay-compliant schema for lists of objects that (1) have their own IDs and (2) are likely to be paginated. Feel free to add a nodes shortcut field to the Connection type (for users who want to skip over edges).
  • If you publish your extension, consider prefixing the package name with graphbrainz-extension- and having the default export of its main entry point be the extension object. That way, using it is as simple as adding the package name to the list of extensions.
  • Consider using graphql-markdown to document the schema created by your extension; this will match how GraphBrainz itself is documented. You can use the diffSchema function to document only the schema updates, see scripts/build-extension-docs.js for how this is done with the built-in extensions.