Codementor Events

How to create a loopback.js mixin — on content author example.

Published Mar 27, 2019

This tutorial shows how to write a custom loopback.js mixins that creates an author field based on the current logged in user.

Go to the profile of akkonrad

akkonrad

But actually, what are mixins?

According to loopback.js docs:

Use mixins to apply common logic to a set of models. For example, a timestamp mixin could inject “created” and “modified” properties to model definitions.

You can apply mixins to any model, including built-in models.

So mixins are pieces of code that can be applied across many of your application models. Like in the above example — it could set created and updated fields of model with minimal effort.

So how to define a new mixin?

Before we create our first mixin, we need to decide where we will put it, and then we need to configure loopback to look for mixins in that location. So let’s say we will create a separate directory for mixins in our /common directory. So then we need to update our model-config.json configuration file:

// server/model-config.json

"_meta": {
  "sources": [
    ...
  ],
  "mixins": [
    "loopback/common/mixins",
    "loopback/server/mixins",
    "../common/mixins"
  ]
},
...

Now, we need to create a mixin itself under the given location. The name of the file needs to be written in kebab-case style (eg. content-author.js), so let’s create it.

// common/mixins/content-author.js

'use strict';

module.exports = function contentAuthor(Model, options) {
  // ...
};

For now our mixin does nothing. And we want to set author field to currently logged in user. To use that we can define so called Operation hooks, pieces of code that are triggered before content is acessed, saved or deleted. In our case, we want to before save hook, so every time that model is saved we will populate the author field. Here is what it could look like:

// common/mixins/content-author.js

'use strict';

module.exports = function contentAuthor(Model, options) {
 
  Model.observe('before save', function event(ctx, next) {

// get current user ID
    const authorId = ctx.options.accessToken.userId;

// set authorId in our instance
    if (ctx.instance) {
      ctx.instance.authorId = authorId;
    } else {
      ctx.data.authorId = authorId;
    }

// next callback in the stack.
    next();
  });

};

And that’s it.

Let’s take a closer look at the code above: function contentAuthor takes 2 parameters: current Model (eg. Article, Product, Project etc.) and **options ** — a configuration of our mixin for the current Model. We will get back to it in the next section. 
Why are we considering two cases (ctx.instance and ctx.data)? Well, the second case is used when we are calling findOrCreate function that first looks for the content, and if it doesn’t find anything, it will create it. So data is the object that will be sent to the connector (saved to the database).

Activate mixin in the model

We want to let loopback.js know at we want to use that mixin with certain models. To do that, we need to open our model file, let’s say /common/models/article.json and specify a list of mixins that we want to use:

// common/models/article.json
{
  "name": "Article",
  "base": "PersistedModel",
  "idInjection": true,
  "mixins": {
    "ContentAuthor": true,
  },
  "properties": { // ... },
  "relations": {
    "author": {
      "type": "belongsTo",
      "model": "User",
      "foreignKey": ""
    }  
}

Our mixin file name is content-author.js, so in configuration we need to refer to our mixin as ContentAuthor ( PascalCase style). Keep in mind that we have defined a relation with User model that will hold that information about the Article author.

Mixin configuration

We could finish our mixin here, it would work. But what if one of our models author field will be called in a different way, eg. userId? We would like our mixin to be more flexible and configurable per each model, so we could decide what the name of the author field should be. So what we could do here is to pass the options object (as we mentioned above):

// common/models/article.json
{
  "name": "Article",
  "base": "PersistedModel",
  "idInjection": true,
  "mixins": {
    "ContentAuthor": { "authorField": "userId" },
  },
  "properties": { // ... },
  "relations": {
    "author": {
      "type": "belongsTo",
      "model": "User",
      "foreignKey": ""
    }  
}

Now we just need to update our mixin code accordingly:

'use strict';

module.exports = function contentAuthor(Model, options) {

  // Set default model options that can be overriden.
  var defaultOptions = {
    authorField: 'authorId',
  };

  // Merge with current model options.
  Object.assign(defaultOptions, options);

  Model.observe('before save', function event(ctx, next) {

const authorId = ctx.options.accessToken.userId;

if (ctx.instance) {
      ctx.instance[defaultOptions.authorField] = authorId;
    } else {
      ctx.data[options.authorField] = authorId;
    }
    next();
  });
};

This way we can build flexible and powerfull mixins.

Summary

I hope now you understand how to create your own mixins. This is a great way to provide reusable code snippets, easily maintanable, highly customizable and still pretty easy to implement.

Here is a git repository of the author mixin and a corresponding node module. Please read the readme file, because configuration there is slightly different.

Github repo: https://github.com/akkonrad/loopback-mixin-demo
NPM module: https://www.npmjs.com/package/loopback-author-mixin

Discover and read more posts from Konrad Kaliciński
get started
post commentsBe the first to share your opinion
Show more replies