Using express.js GET Ressource in in-repo-addon


#1

Hi all,

I search an example where I can define REST Ressources ( GET, POST, … ) in a simple in-repo-addon for the development time until I have a real node project for my backend etc…

I created my in-repo-addon by ember-cli. This is my addon package.json - ( its a copy of an running example from another project )::

{
  "name": "test-backend",
  "keywords": [
    "ember-addon"
  ],
  "ember-addon": {
    "before": [
      "serve-files-middleware",
      "history-support-middleware",
      "proxy-server-middleware"
    ]
  }
}

… and this is my Test-Server with my first Ressource:

/* eslint-env node */
'use strict';

module.exports = {
  name: 'test-backend',

  isDevelopingAddon() {
    return true;
  },

  serverMiddleware({app}) {
    console.log('init test backend - /api - v1.0');

    app.get('/activities', (req, res) => {
      console.log(req.params);
    });
    
  }
};

If I start my server there are no errors in my log. But my application is stopping with

GET http://localhost:4200/api/activities 404 (Not Found)

I do not know what is wrong… :frowning: No help found in documentation or google too…

Best regards, Mario


#2

Hi Mario, I would not recommend trying to hack a node server into an ember project. It may be possible but I suspect you’re going to run into a lot of issues. Since Ember is intended to be a front-end framework only there’s no support for building server type resources into an Ember project.

As an alternative I’d highly recommend using ember-mirage for building out a “fake” API for dev/testing purposes. It’s easy and powerful and works great with ember out of the box. Basically mirage lets you define an API with dynamically generated fake data, function handlers that are much like a node endpoint, and all kinds of other cool features. It’s invaluable for testing, and can also help a lot with your use case: development with dynamic data but without a finished backend API.


#3

Hi dknutsen,

I use ember-mirage in my project. :slight_smile: But I have my problems by using relations in my models. The documentation has not really goog examples for using relations in models.

In this moment, where I try to define a hasMany or a belongsTo relation in my models, my komplete app crashed. The problem is that mirage do not print any errors in the console.

Greetings Mario


#4

Ah ok cool. Yeah debugging mirage can be a little tricky sometimes. If you post some of your code like your models, your mirage code, which serializer/adapter you’re using (REST, JSON or JSONAPI) and the environment you’re running in (I assume “development”?) I’d be happy to take a look and try to make some suggestions.

For starters you can make sure mirage logging is turned on in your default scenario with:

server.logging = true;

That won’t necessarily help with big errors but it will at least log all of the mirage requests and responses


#5
server.logging = true; 

is already true … :slight_smile: Let me remove all my not needed tests with an in-repo-addon and then I will show you my project … ok.

Give me any minutes. :slight_smile: Mario


#6

So… I removed all artifacts from my addon test. If you like to have a look to my project and my relation problems you can see all here:

Start the app with “s.bat” or “s.sh

The related ember models are “activity” and “category”. Until now no information of “activity.categories” will be rendered in my templates. This would be the next step. The same I ceated and prepared in mirage.

Best regards, Mario


#7

For starters I think you can delete all of the files in /mirage/models, since ~0.3 mirage has support for reading your ember data models and just using them, so it can simplify things. Secondly I think you’ll probably want to set your relationship in your ember model to inverse: null, e.g.:

categories: DS.hasMany('category', { async: true, inverse:  null }),

Sometimes inverses can cause problems in mirage and it doesn’t look like you’re defining an inverse anyway so you might as well be explicit about it.

To create relations in the mirage database I’d do it either in the scenario (when creating activities do a loop and either use specific categories or random ones, and simply pass the category id to the server.create('activity') call) or use traits which are a handy feature of mirage factories that can create related records. For example if you had a shopping website with product categories and you wanted to created 5 categories with 4 items each you could create a “trait” called “with4items” or something on the category factory where once the category is created it creates 4 items with that category. Then you could (in your scenario) just say server.createList('category', 5, 'with4items'); and it would create 5 categories and 20 items, and the 20 items would be evenly spread across the categories. There is some info on traits and creating related records in the mirage docs.


#8

Played around with it a little bit more and got mirage returning related categories for activities. First I made the following changes:

  • deleted /mirage/models and everything in it
  • made the “categories” relationship inverse:null per above
  • added three categories to every activity generated in the scenario
  • added a serializer for “activity” that includes categories

The git diff is as follows:

diff --git a/app/models/activity.js b/app/models/activity.js
index a35445e..ef41290 100644
--- a/app/models/activity.js
+++ b/app/models/activity.js
@@ -3,7 +3,7 @@ import DS from 'ember-data';
 export default DS.Model.extend({
     version: DS.attr('number'),
     active: DS.attr('boolean'),
-    categories: DS.hasMany('category', { async: true }),
+    categories: DS.hasMany('category', { async: true, inverse: null }),
     // organizer: DS.belongsTo('organizer', { async: true }),
     date: DS.attr('date'),
     title: DS.attr('string'),
diff --git a/mirage/models/activity.js b/mirage/models/activity.js
deleted file mode 100644
index 5183b8c..0000000
--- a/mirage/models/activity.js
+++ /dev/null
@@ -1,6 +0,0 @@
-import { Model/*, belongsTo*/, hasMany } from 'ember-cli-mirage';
-
-export default Model.extend({
-    categories: hasMany('category'),
-    // organizer: belongsTo('address')
-});
diff --git a/mirage/models/address.js b/mirage/models/address.js
deleted file mode 100644
index 1486a72..0000000
--- a/mirage/models/address.js
+++ /dev/null
@@ -1,4 +0,0 @@
-import { Model } from 'ember-cli-mirage';
-
-export default Model.extend({
-});
diff --git a/mirage/models/category.js b/mirage/models/category.js
deleted file mode 100644
index 1486a72..0000000
--- a/mirage/models/category.js
+++ /dev/null
@@ -1,4 +0,0 @@
-import { Model } from 'ember-cli-mirage';
-
-export default Model.extend({
-});
diff --git a/mirage/scenarios/default.js b/mirage/scenarios/default.js
index 3ce1bbe..5fd2558 100644
--- a/mirage/scenarios/default.js
+++ b/mirage/scenarios/default.js
@@ -1,12 +1,11 @@
 export default function(server) {
 
   // List of categories (max 15 available!!!)
-  server.createList('category', 15);
+  let categories = server.createList('category', 15);
 
   // List of addresses
   server.createList('address', 20);
 
-  // List of activities
-  server.createList('activity', 100);
+  server.createList('activity', 100, {categories: [categories[2], categories[5], categories[9]]});
 
 }

And the new serializer (/mirage/serializers/activity.js):

import ApplicationSerializer from './application';

export default ApplicationSerializer.extend({
  include: ['categories']
});

#9

Wow… That I have to translate and think about tomorrow. :slight_smile: My english is not the best. :slight_smile:

Best regards, Mario


#10

So… I have try many varaints to set a hasMany relation for my activity but I do not understand the concept how I have to describe my relation etc…

Currently I have this error:

Uncaught TypeError: model.hasInverseFor is not a function

This is the code of my models and mirage files:

Model “activity”

export default DS.Model.extend({
    ...
    categories: DS.hasMany('category', { async: true, inverse: null }),
    ...
})

Model “category”

export default DS.Model.extend({
    ...
    activity: DS.belongsTo('activity')
    ...
})

>>> No mirage (v0.3) models are defined.

Mirage scenario

export default function(server) {
  server.createList('category', 15);
  server.createList('activity', 100);
}

Mirage factory “activity”

import { Factory } from 'ember-cli-mirage';

export default Factory.extend({
    id(i) {
        return `activity_${i}`;
    },
    categories() {
        return [
            faker.random.number(14),
            faker.random.number(14),
            faker.random.number(14)
        ];
    }
    ...

Mirage factory “category”

import { Factory } from 'ember-cli-mirage';

const categories = ['*','music','excursion','event','opening','food','relaxation','dance','family','guides','culture','exhibition','market','meetup','others'];

export default Factory.extend({
    id(i) {
        return `${i}`;
    },
    name() {
        return categories[Math.floor(Math.random() * 14) + 1];
    }
});

What is still wrong?

If I make the property array “categories” empty of the factory “activity”, there is no error but of corse no categories will be set/linked as relation within the activity instances.

Changed property “categories” in mirage factory “activity”

categories() {
    return [];
}

Best regards, Mario


#11

1a) I would avoid trying to set related data in the factory properties, do it in the scenario or a factory trait instead. To do it in a factory trait, your category factory would look like this:

import { Factory, trait } from 'ember-cli-mirage';

const categories = ['*','music','excursion','event','opening','food','relaxation','dance','family','guides','culture','exhibition','market','meetup','others'];

export default Factory.extend({
  id(i) {
    return `${i}`;
  },
  name() {
    return categories[Math.floor(Math.random() * 14) + 1];
  },

  // this is the trait
  withThreeActivities: trait({
    // once the category is created, the category can create activities with itself as the linked category
    afterCreate(category, server) {
      let activities = server.createList('activity', 3, { category });
      category.activities = activities;
    }
  })
});

Then your scenario would just look like this:

export default function(server) {
  server.createList('category', 15, 'withThreeActivities');
}

So each time it creates one of the 15 categories, it will create 3 activities with that category set on the activity, and then it sets those 3 activities on the category.

1b) An alternative to the above might be just doing this in your scenario:

export default function(server) {
  let categories = server.createList('category', 15);
  categories.forEach(category => {
    for(var i=0; i<8; i++) {
      category.createActivity();
    }
  });
}

See the mirage model docs for more on that, especially the part at bottom on hasMany

  1. to clear up your mirage error, either make both relationships (the belongsTo and the hasMany) inverse: null OR try setting them to each other explicitly, e.g.
export default DS.Model.extend({
    ...
    activity: DS.belongsTo('activity', inverse: 'categories')
    ...
})
export default DS.Model.extend({
    ...
    categories: DS.hasMany('category', { async: true, inverse: 'activity' }),
    ...
})

#12

Very very thanks for your patience and help. :slight_smile:

I tested your example and adapt for me. Now I have … no more errors, created categories and created activities too (So far so good…) but in my rendered template of my activities list the “categories” property of each activities are still empty… :frowning:

Can you have a look to my project on github again? :slight_smile:

My structure have to be so that an activity can have any asigned categories inside. A category have only a property with the related relation back to the activity.

  1. In my scenario first I generate all available catgories.
  2. In step two I generate all activities and set a random array of categories into the related property categories into each activity.

I have absolute no ideas whats still wrong.

Mario


#13

You’re not telling mirage to return related categories for your activities. As I mentioned somewhere above to accomplish this the easiest way you’ll also need a mirage serializer for activity that sets categories as included:

/mirage/serializers/activity.js:

import ApplicationSerializer from './application';

export default ApplicationSerializer.extend({
  include: ['categories']
});

Then mirage will receive a request for activities and say “oh, you also want me to include the categories linked to this activity” so it will return the data. I think you could also accomplish this in your mirage/config.js file but it’s a lot easier to do it in the mirage serializer.


#14

Hi dknutsen,

Very very thanks again for all your tipps. IT WORKS PERFECT.

I added the serializer and in my scenario I generate all categories, all activities and I assigned all categories to the related activities by this way:

server.create('activity', { categories: randomCategoriesSelection });

One last question: :slight_smile: In the real world in my activity I have only an array with all ID’s of the linked categories and not an array with real model objects because I load my cattegories of my activities dynamically in background per REST.

Is there an similar way with mirage to use IDs too?

Greetings Mario


#15

Ah yeah you’re right that does sideload the records, try the following instead:

import ApplicationSerializer from './application';

export default ApplicationSerializer.extend({
//  include: ['categories']
  serializeIds: 'always'
});

#16

Hm… sorry that I do not stop with my questions and problems but when I use …

import ApplicationSerializer from './application';

export default ApplicationSerializer.extend({
  serializeIds: 'always'
});

… in my serializer the Ember will crash in the has-many.js by line 219

models.forEach(function (model) {
    if (model.hasInverseFor(association)) {
        var inverse = model.inverseFor(association);

        model.associate(_this2, inverse);
    }
});

The problem here is that the parameter model is e.g. 1, 2, 3,… like my ids in my activity. This is my test in my scenario:

server.create('activity', { categories: [1,4,9] });

Greetings, Mario