@northernv/dataloader

A framework for dataloaders in models

Usage no npm install needed!

<script type="module">
  import northernvDataloader from 'https://cdn.skypack.dev/@northernv/dataloader';
</script>

README

@northernv/dataloader

npm MIT licensed

Usage

npm install -S @northernv/dataloader;
yarn add @northernv/dataloader;

In your models, that are Javascript classes, include a static function called loaders that returns an object of dataloaders

class Property {
  static loaders (DataLoader, { db }) {
    const manyPropertyById = new DataLoader(propertyIds => {
      return db('property').whereIn('id', propertyIds)
        .then(rows => propertyIds.map(id => rows.filter(x => x.id === id)))
    })

    const manyPropertyByAssociationId = new DataLoader(associationIds => {
      return db('property').whereIn('property.assocation_id', associationIds)
        .then(rows => associationIds.map(id => rows.filter(x => x.assocationId === id)))
    })

    const onePropertyById = new DataLoader(propertyIds => {
      return db('property').whereIn('id', propertyIds)
        .then(rows => propertyIds.map(id => rows.find(x => x.id === id)))
    })
    return { manyPropertyById, manyPropertyByAssociationId, onePropertyById }
  }
}

Each loader should follow the patter one<ClassName>By<PropertyName> or many<ClassName>By<PropertyName>

In your bootstrapping file

const loader = dataloaders('models', { db })

const server = new ApolloServer({
  schema,
  // Create new Dataloaders for each request
  context: ({ req, res }) => new Context({ req, res, db, channel, loaders: loader() }),
  formatError: (error) => {
    Sentry.captureException(error)
    return { ...error, code: get(error, 'originalError.constructor.name', 'Unknown') }
  },
})

or you can delay adding the db option until runtime

// Notice here we do not pass in the run time option `db`
const loader = dataloaders('models')

const server = new ApolloServer({
  schema,
  // Create new Dataloaders for each request
  // Instead we pass in the runtime option `db` here on init
  context: ({ req, res }) => new Context({ req, res, db, channel, loaders: loader({ db }) }),
  formatError: (error) => {
    Sentry.captureException(error)
    return { ...error, code: get(error, 'originalError.constructor.name', 'Unknown') }
  },
})

See how we pass in the model directory that is relative to the project root and our options. The options will be passed to each of the loaders functions

Notes

  • It will throw an error if there are two loaders with the same name, so sticking to the naming convention is important.
  • Many loaders use filter, while One loaders user find
  • It assumes that items are loaded from a database