# Feathers Js

FeathersJS Guide - Coding Garden

Awesome Feathers

Feathers is a framework for real-time applications and REST APIs.

# Quick guide

npm i @feathersjs/feathers

npm i @feathersjs/express @feathersjs/socketio


1
2
3
4
5

# Starting a project

To start a project is as simple as:

npm install -g @feathersjs/cli

mkdir my-app

cd my-app

feathers generate app

npm start

1
2
3
4
5
6
7
8
9
10

# Somethings to be familiarized with before start FeathersJS

# Importing modules

CommonJS Modules

const feathers = require('@feathersjs/feathers');

const app = feathers();
1
2
3

ES6 Modules

import feathers from '@feathersjs/feathers';

const app = feathers();
1
2
3

# Promises and async/await

https://mdn.io/async_function

# async function

The async function declaration defines an asynchronous function — a function that returns an AsyncFunction object. Asynchronous functions operate in a separate order than the rest of the code via the event loop, returning an implicit Promise as its result. But the syntax and structure of code using async functions looks like standard synchronous functions.

You can also define async functions with an async function expression.

Things we may want to do:

async function createMessage(){
    const created = await app.service('messages).create({
        text: 'Hello Feathers'
    });
    console.log(created);
}
1
2
3
4
5
6

JavaScript Demo: Statement - Async

function resolveAfter2Seconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('resolved');
    }, 2000);
  });
}

async function asyncCall() {
  console.log('calling');
  var result = await resolveAfter2Seconds();
  console.log(result);
  // expected output: 'resolved'
}

asyncCall();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

Syntax

async function name([param[, param[, ... param]]]) {
   statements
}
1
2
3

# Promise

Things we may want to do:

app.service('messages').create({
    text: 'Hello Feathers'
}).then(created => console.log(created));
1
2
3

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise

The Promise object represents the eventual completion (or failure) of an asynchronous operation, and its resulting value.

JavaScript Demo: Promise Constructor:

var promise1 = new Promise(function(resolve, reject) {
  setTimeout(function() {
    resolve('foo');
  }, 300);
});

promise1.then(function(value) {
  console.log(value);
  // expected output: "foo"
});

console.log(promise1);
// expected output: [object Promise]
1
2
3
4
5
6
7
8
9
10
11
12
13

# REST APIs

https://w.wiki/7iv

The following table shows how HTTP methods are typically used in a RESTful API.

HTTP METHODS Collection resthece, such as
api.example.com/collection/
Member resthece, such as
api.example.com/collection/item3
GET Retrieve the URIs of the member restheces of the collection resthece in the response body. Retrieve representation of the member resthece in the response body.
POST Create a member resthece in the collection resthece using the instructions in the request body. The URI of the created member resthece is automatically assigned and returned in the response Location header field. Create a member resthece in the member resthece using the instructions in the request body. The URI of the created member resthece is automatically assigned and returned in the response Location header field.
PUT Replace all the representations of the member restheces of the collection resthece with the representation in the request body, or create the collection resthece if it does not exist. Replace all the representations of the member resthece or create the member resthece if it does not exist, with the representation in the request body.
PATCH Update all the representations of the member restheces of the collection resthece using the instructions in the request body, or may create the collection resthece if it does not exist. Update all the representations of the member resthece, or may create the member resthece if it does not exist, using the instructions in the request body.
DELETE Delete all the representations of the member restheces of the collection resthece. Delete all the representations of the member resthece.

The GET method is safe, meaning that applying it to a resthece does not result in a state change of the resthece (read-only semantics).

The GET, PUT and DELETE methods are idempotent, meaning that applying them multiple times to a resthece result in the same state change of the resthece as applying them once, though the response might differ.

# WebSockets


https://mdn.io/websockets


The WebSocket API is an advanced technology that makes it possible to open a two-way interactive communication session between the user's browser and a server. With this API, you can send messages to a server and receive event-driven responses without having to poll the server for a reply.

# Starting with a very simple backend Feathers Service

First create a folder

mkdir feathers-basics
1
cd feathers-basics/
1

Then initiate a package json inside the folder with npm init -y

npm init -y
1
{
  "name": "feathers-basics",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}
1
2
3
4
5
6
7
8
9
10
11
12

and open it with code:

code .
1

Now install the FeathersJS dependencies:

npm i @feathersjs/feathers
1

Now we can create and App.js file to start using Feathers

Inside the App.js

const feathers = require('@feathersjs/feathers');
const app = feathers();

class MessageService {
    constructor(){
        this.messages = [];
    }
    async find() {             // If anyone calls the find method 
        // they just gonna get this array of messages
        return this.messages;
    }

    async create(data) {
        const message = {
            id: this.messages.length,
            text: data.text        
        };
        this.messages.push(message);
        return message;
    }
  // Now we have a super simple service that implements the find 
    // method and the create method.
 // Now we can add this service to the feathers app and use the 
    // features of feathers to interact with the service.

}

// First we register the service
// We    name it and pass an instance 
// Where app is the feathers app that we've created and we are 
// registering an instance of this message service with the name message.

app.use('messages', new MessageService());



// if we want to know anytime that a message was created we do:

app.service('messages').on('created', (message) => {
    console.log('A new message has been created', message);
})


// Now we can use the services


const main = async () => {
    await app.service('messages').create({
        text: 'Hello Feathers'
    });
    await app.service('messages').create({
        text: 'Hello Again'
    });

    const messages = await app.service('messages').find();
    console.log('All Messages', messages);
}


main();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

To run the App we go node app.js on the terminal

node app.js
1
A new message has been created { id: 0, text: 'Hello Feathers' }
A new message has been created { id: 1, text: 'Hello Again' }
All Messages [ { id: 0, text: 'Hello Feathers' }, { id: 1, text: 'Hello Again' } ]
1
2
3

# Expose a Feathers Service over REST and WebSockets

# First step - Install Dependencies and create a express application and pass a feathers application

npm i @feathersjs/express @feathersjs/socketio
1

Now that we have those dependencies we can bring them in:

const express = require('@feathersjs/express');
const socketio = require('@feathersjs/socketio');
1
2

we also move the creation of the app below the message service

const app = feathers();
1

but instead of creating a feather application directly we will create a express application and pass a feathers application in to it.

This enables feathers to interact with express and adds a few things on top of it.

const app = express(feathers());
1

# Middleware

Now we are going to setup some middleware just like we would in a express application.

A Json body parsing middleware, so if we are making post request with json data he will know how to interpret it.

app.use(express.json());
1

Now a middleware that will parse incoming form data. So when REST clients are sending data to the server this two middleware will know how to interpret it.

app.use(express.urlencoded({ extended:true }));
1

Now a static server that will serve up the clients:

app.use(express.static(__dirname));
1

# Configuring the application

We are going to configure the app to use the REST protocol through express and the socket protocol through the socketio package.

With this line of code below we will enabled the services to be available over RESTful API

app.configure(express.rest())
1

And with this one we have exposed the feather application over sockets

app.configure(socketio());
1

A important change:

app.use('messages', new MessageService())
1

will now have a slash / , This will be like registering a route in Express.

app.use('/messages', new MessageService())
1

Last thing to do will be to use the error service.

app.use(express.errorHandler());
1

# Rewriting the application

Setting up socket channels, this will be called anytime a client connects to the app. Then we are going to join that connection to everybody channel.

app.on('connection', connection => {
    app.channel('everybody').join(connection);
})
1
2
3

Now to anytime that an event happened (in this case a message is created), we publish that event to everyone on the everybody channel.

app.publish(() => app.channel('everybody'));
1

Last thing will be to listen on port 3030

app.listen(3030).on('listening', () => {
    console.log('Feathers server listening on localhost:3030');
});
1
2
3

To see that in action:

app.service('messages').create({
    text: 'Hello world from the server!'
});
1
2
3

Now we can start the application:

Thiago Souto@MSI MINGW64 ~/Desktop/feathers-basics
$ node app.js
Feathers server listening on localhost:3030
1
2
3

by visiting http://localhost:3030/messages we get:

[{"id":0,"text":"Hello world from the server!"}]
1

New app.js:

const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
const socketio = require('@feathersjs/socketio');

class MessageService {
    constructor(){
        this.messages = [];
    }
    async find() {             // If anyone calls the find method they just gonna get this array of messages
        return this.messages;
    }

    async create(data) {
        const message = {
            id: this.messages.length,
            text: data.text        
        };
        this.messages.push(message);
        return message;
    }
  // Now we have a super simple service that implements the find method and the create method.
 // Now we can add this service to the feathers app and use the features of feathers to interact with the service.
}

const app = express(feathers());  // this was moved from the top

// middleware to interpret data sent to the server:

app.use(express.json());
app.use(express.urlencoded({ extended:true }));
app.use(express.static(__dirname)); // just serving the directory

// Now configuring the app:

app.configure(express.rest())
app.configure(socketio());

// First we register the service
// We    name it and pass an instance 
// Where app is the feathers app that we've created and we are registering an instance of this message service with the name message.

app.use('/messages', new MessageService());


app.use(express.errorHandler()); // using error handling 

// rewriting last program.

// Setting up socket channels, this will be called anytime a client connects to the app. Then we are going to join that connection to everybody channel.

app.on('connection', connection => {
    app.channel('everybody').join(connection);
})

//Now to anytime that an event happened (in this case a message is created), we publish that event to everyone on the everybody channel.

app.publish(() => app.channel('everybody'));

//listen on port 3030

app.listen(3030).on('listening', () => {
    console.log('Feathers server listening on localhost:3030');
});

//To see that in action:

app.service('messages').create({
    text: 'Hello world from the server!'
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69

Output at http://localhost:3030/messages

[
    {
        "id": 0,
        "text": "Hello world from the server!"
    }
]
1
2
3
4
5
6

# 05 - Build a Simple Feathers Client

First we create the index.html file.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Hello Feathers</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
</head>
<body>
    <main id="main" class="container mx-auto text-3xl font-bold">
        <h1>Welcome to Feathers</h1>
        <form onsubmit="sendMessage(event.preventDefault())">
            <input type="text" id="message-text" placeholder="Enter text here">
            <button type="submit" class="btn btn-primary">Send Message</button>
        </form>
    </main>

    <script type="text/javascript" src="//cdnjs.cloudflare.com/ajax/libs/core-js/2.1.4/core.min.js"></script>
    <script src="//unpkg.com/@feathersjs/client@^3.0.0/dist/feathers.js"></script>
    <script src="//unpkg.com/socket.io-client@1.7.3/dist/socket.io.js"></script>
    <script>
      // Socket.io is exposed as the `io` global.
      const socket = io();
      // @feathersjs/client is exposed as the `feathers` global.
      const app = feathers();

      // Set up Socket.io client with the socket
      app.configure(feathers.socketio(socket));

      async function sendMessage() {
        console.log('Submit');
        const messageInput = document.getElementById('message-text')
        await app.service('messages').create({
          text: messageInput.value
        });
        messageInput.value = '';
      }

      function addMessage(message) {
        const messageElement = document.createElement("p");
        messageElement.textContent = message.text;
        document.getElementById('main').appendChild(messageElement);
      }

      const main = async () => {
        const messages = await app.service('messages').find();
        messages.forEach(addMessage);

        app.service('messages').on('created', addMessage);
      }

      main();
    </script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

# 06 - Feathers Guide - Feathers CLI - Generate an App

First Install the CLI npm i -g @feathersjs/cli

then:

mkdir backend

cd backend/
        
feathers generate app
1
2
3
4
5
Thiago Souto@MSI MINGW64 ~/Desktop/DESKTOP FOLDERS/SKINNY ROBOT/DEV/ITAMAR BOOK
$ mkdir backend                                                                

Thiago Souto@MSI MINGW64 ~/Desktop/DESKTOP FOLDERS/SKINNY ROBOT/DEV/ITAMAR BOOK
$ cd backend/                                                                  

Thiago Souto@MSI MINGW64 ~/Desktop/DESKTOP FOLDERS/SKINNY ROBOT/DEV/ITAMAR BOOK/backend
$ feathers generate app                                                                
? Do you want to use JavaScript or TypeScript? JavaScript
? Project name backend
? Description                                        
? What folder should the source files live in? src                                    
? Which package manager are you using (has to be installed globally)? npm                               
? What type of API are you making? REST, Realtime via Socket.io
? Which testing framework do you prefer? Mocha + assert
? This app uses authentication Yes
? Which coding style do you want to use? ESLint
? What authentication strategies do you want to use? (See API docs for all 180+ supported oAuth providers) Username + Password (Local)
? What is the name of the user (entity) service? users
? What kind of service is it? MongoDB
? What is the database connection string? mongodb://localhost:27017/backend
   create package.json       
   create config\default.json
   create public\favicon.ico 
   create public\index.html  
   create .editorconfig      
   create src\app.hooks.js   
   create src\channels.js
   create src\index.js
   create src\logger.js
   create src\middleware\index.js
   create src\services\index.js
   create .gitignore
   create README.md
   create src\app.js
   create test\app.test.js
   create .eslintrc.json
   create config\production.json
   create config\test.json
   create src\services\users\users.service.js
   create src\authentication.js
   create test\authentication.test.js
   create src\mongodb.js
   create src\services\users\users.class.js
   create src\services\users\users.hooks.js
   create test\services\users.test.js
npm WARN deprecated uuid@3.4.0: Please upgrade  to version 7 or higher.  Older versions may use Math.random() in certain circumstances, which is known to be problematic.  See https://v8.dev/blog/math-random for details.

added 109 packages, and audited 110 packages in 3s

9 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

added 2 packages, and audited 112 packages in 816ms

10 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

added 23 packages, and audited 135 packages in 1s

13 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities

up to date, audited 135 packages in 820ms

13 packages are looking for funding
  run `npm fund` for details

found 0 vulnerabilities
npm WARN deprecated @types/socket.io-parser@3.0.0: This is a stub types definition. socket.io-parser provides its own type definitions, so you do not need this installed.
npm WARN deprecated debug@4.1.1: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debu
g/issues/797)
npm WARN deprecated debug@4.1.1: Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debu
g/issues/797)

added 82 packages, and audited 217 packages in 2s

16 packages are looking for funding
  run `npm fund` for details

3 high severity vulnerabilities

Some issues need review, and may require choosing
a different dependency.

Run `npm audit` for details.
npm ERR! code ETARGET
npm ERR! notarget No matching version found for workerpool@6.2.0.
npm ERR! notarget In most cases you or one of your dependencies are requesting
npm ERR! notarget a package version that doesn't exist.

npm ERR! A complete log of this run can be found in:
npm ERR!     C:\Users\Thiago Souto\AppData\Local\npm-cache\_logs\2022-02-18T04_04_47_481Z-debug-0.log

Make sure that your mongodb database is running, the username/role is correct, and "mongodb://localhost:27017/backend" is reachable and the database has been created.
Your configuration can be found in the projects config/ folder.

> backend@0.0.0 lint
> eslint src/. test/. --config .eslintrc.json --fix "--fix"

'eslint' is not recognized as an internal or external command,
operable program or batch file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109

# Feathers Guide - Overview of Services and generate a service

  • Reading and/or writing from a database
  • Interacting with the file system
  • Call another API
  • Call other services like
    • Sending an email
    • Processing a payment
    • Returning the current weather for a location, etc.

# Service methods

Service methods are CRUD methods that a service can implement


Services

When used as a REST API, incoming requests get mapped automatically to their corresponding service method.

Methods that don't modify:


Services

Methods that modify:


Services


Services


Services


Services

Feather have many Database adapters.


Services


Services

# Generating a Feathers Service

feathers generate service
1
Thiago Souto@MSI MINGW64 ~/Desktop/DESKTOP FOLDERS/SKINNY ROBOT/DEV/ITAMAR BOOK/backend
$ feathers generate service
? What kind of service is it? MongoDB
? What is the name of the service? messages                   
? Which path should the service be registered on? /messages
? Does the service require authentication? Yes
   create src\services\messages\messages.service.js
    force src\services\index.js                    
   create src\services\messages\messages.class.js  
   create src\services\messages\messages.hooks.js  
   create test\services\messages.test.js           

up to date, audited 540 packages in 2s

1
2
3
4
5
6
7
8
9
10
11
12
13
14

# 12 - Feathers Guide - Override a method in a Service

Now we will modify the user service to override the create method to add some custom logic in the create method.

Everytime the user is created we are automatically going to add an avatar property to that user that is an avatar image. And we are using Gravatar for that.

This is user.class.js before

const { Service } = require('feathers-mongodb');

exports.Users = class Users extends Service {
  constructor(options, app) {
    super(options);

    app.get('mongoClient').then(db => {
      this.Model = db.collection('users');
    });
  }
};

1
2
3
4
5
6
7
8
9
10
11
12

The gravatar service works by passing a md5 hash of someone's email and it will give back an avatar image.

const { Service } = require('feathers-mongodb');

const crypto = require('crypto');

const gravatarUrl = 'https://s.gravatar.com/avatar';
const query = 's=60';

exports.Users = class Users extends Service {
  constructor(options, app) {
    super(options);

    app.get('mongoClient').then(db => {
      this.Model = db.collection('users');
    });
  }
  create(data, params) {
    const { email, password, googleId, facebookId } = data;

    const hash = crypto.createHash('md5').update(email.toLowerCase()).digest('hex');

    const avatar = '${gravatar}/${hash}?${query}';

    const userData = {
      email,
      password,
      googleId,
      facebookId,
      avatar
    };

    return super.create(userData, params);
  }

};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

# 13 - Feathers Guide - Intro to hooks and generate custom hooks

Hooks are small middleware functions that can be executed before, after or on error of any given service method.


Hooks

Here, any data that passes through this hood will have a new property on it called createdAt.

And in the messages service we are registering this hook in the before create, so before any message is created its going to add that property.

const createdAt = async context => {
  context.data.createdAt = new Date();
  
  return context;
};

app.service('messages').hooks({
  before: {
    create: [createdAt]
  }
});
1
2
3
4
5
6
7
8
9
10
11

We can also reuse it. In this case, when a particular message is updated we are going to set the updatedAt property on that message.

const setTimestamp = name => {
  return async context => {
    context.data[name] = new Date();

    return context;
  }
};

app.service('messages').hooks({
  before: {
    create: [ setTimestamp('createdAt') ],
    uptade: [ setTimestamp('updatedAt')]
  }
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14

About the Hook context object

Normally we see the before, after and error hooks for every application

module.exports = {
  before: {
    all: [],
    find: [ authenticate('jwt') ],
    get: [ authenticate('jwt') ],
    create: [ hashPassword('password') ],
    update: [ hashPassword('password'),  authenticate('jwt') ],
    patch: [ hashPassword('password'),  authenticate('jwt') ],
    remove: [ authenticate('jwt') ]
  },

  after: {
    all: [ 
      // Make sure the password field is never sent to the client
      // Always must be the last hook
      protect('password')
    ],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  },

  error: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: []
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35

# Let's generate a hook

We will now create a hook that verifyies when a user send a message if the message has a text property , is of certain length, and will add a createdAt property timestamp on it.

feathers generate hook
1
Thiago Souto@MSI MINGW64 ~/Desktop/DESKTOP FOLDERS/SKINNY ROBOT/DEV/ITAMAR BOOK/backend
$ feathers generate hook
? What is the name of the hook? process-message
? What kind of hook should it be? before                                   
? What service(s) should this hook be for (select none to add it yourself)?                                                                  
 messages                                                                                                                                    
? What methods should the hook be for (select none to add it yourself)? create
   create src\hooks\process-message.js           
    force src\services\messages\messages.hooks.js

1
2
3
4
5
6
7
8
9
10

process-message.js

// Use this hook to manipulate incoming or outgoing data.
// For more information on hooks see: http://docs.feathersjs.com/api/hooks.html

// eslint-disable-next-line no-unused-vars
module.exports = (options = {}) => {
  return async context => {
    const { data } = context;

    if (!data.text) {
      throw new Error('A message must have text');
    }

    const { user } = context.params;

    // this will chop off anything over 400
    const text = data.text.substring(0, 400);

    context.data = {
      text,
      // by putting the user here we can associate a msg to him
      userId: user._id,
      createdAt: new Date().getTime()
    }

    // At the bottom of every hook you have to return the context
    return context;
  };
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

We wil now create a hook for the outgoing message so any time we request messages from our server, we willl automatically populate the user data.

Thiago Souto@MSI MINGW64 ~/Desktop/DESKTOP FOLDERS/SKINNY ROBOT/DEV/ITAMAR BOOK/backend
$ feathers generate hook
? What is the name of the hook? populate-user     
? What kind of hook should it be? after                                    
? What service(s) should this hook be for (select none to add it yourself)?                                                                  
 messages                                                                                                                                    
? What methods should the hook be for (select none to add it yourself)? all
   create src\hooks\populate-user.js             
    force src\services\messages\messages.hooks.js

1
2
3
4
5
6
7
8
9
10
// Use this hook to manipulate incoming or outgoing data.
// For more information on hooks see: http://docs.feathersjs.com/api/hooks.html

// eslint-disable-next-line no-unused-vars
module.exports = (options = {}) => {
  return async context => {
    //destructure all the things we will need from the context
    const { app, method, result, params } = context;

    const addUser = async message => {
      const user = await app.service('users').get(message.userId, params);
      return {
        ...message,
        user
      };
    };

    //logic to figure it out what type of hook we are in
    if (method === 'find') {
      result.data = await Promise.all(result.data.map(addUser));
    } else {
      context.result = await addUser(result);
    }

    return context;
  };
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

# 14 - Feathers Guide - When to customize a service vs create a hook

When you think should I override a service or should I write a hook, it really comes down to is this logic particular to a given service?

In this case we think that the logic is particular to the service because it's not really anyone else's possibility to add the avatar to the user.

And in the case of hooks, this is usually things that can be reused often.

When do you create a custom service?

Usually when you are dealing with external API's

TIP

Error:

$ npm run dev

> backend@0.0.0 dev
> nodemon src/

'nodemon' is not recognized as an internal or external command,
operable program or batch file.
1
2
3
4
5
6
7

Solution:

# npm install -g nodemon

Nodemon refreshes the script everytime we make a change.

To track a script jus add "devStart": "nodemon script.js" to the package.json and execute npm run devStart, now every change will update the script.

# 15 - Feathers Guide - Use Postman to Create a User and View Messages

$ npm run dev

> backend@0.0.0 dev
> nodemon src/

[nodemon] 2.0.15
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: js,mjs,json
[nodemon] starting `node src/`
info: Feathers application started on http://localhost:3030

1
2
3
4
5
6
7
8
9
10
11
12

Now we have our Feathers app running on port 3030, so we can make a request to it using postman

Here we are posting an email and password to the users service to register a user.

http://localhost:3030/users

{
  "email": "thiago@feathers.com",
  "password": "notsosecret"
}
1
2
3
4

Create User

Here we are going to use a local strategy authentication to get a token, which means that our login worked.

http://localhost:3030/authentication

{
  "strategy": "local",
  "email": "thiago@feathers.com",
  "password": "notsosecret"
}
1
2
3
4
5

Create User

Subsequent requests to this service or any of the services in the app will need the token.

If we don't have the token the response will be:

http://localhost:3030/messages


Create User

And with the token, and set a Authorizatiopn header with a Bearer and the token, it will be:

Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6ImFjY2VzcyJ9.eyJpYXQiOjE2NDUxNzMzMDMsImV4cCI6MTY0NTI1OTcwMywiYXVkIjoiaHR0cHM6Ly95b3VyZG9tYWluLmNvbSIsImlzcyI6ImZlYXRoZXJzIiwic3ViIjoiNjIwZjU4ZmUxYjA3Y2E3ODRhOTA3ZWU3IiwianRpIjoiMmQzN2IzYmItMGFmOC00MWMxLWEwNWMtYzg3M2NhYzA4N2YwIn0.WkbZLwQWFdN00jrNU6FHMhI5lGsdHbMmfUkiIgenmOg
1

Create User

# 16 - Feathers Guide - Feathers Client Authentication Basics

Now we are going to use the autheticated user generated from Postman and get a token. this will be hardcoded in the client.js file in the public folder.

/public/index.html

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <meta name="viewport"
          content="width=device-width, initial-scale=1.0, maximum-scale=1, user-scalable=0" />
    <title>FeathersJS chat</title>
    <link rel="shortcut icon" href="favicon.ico">
    <link rel="stylesheet" href="//unpkg.com/feathers-chat@4.0.0/public/base.css">
    <link rel="stylesheet" href="//unpkg.com/feathers-chat@4.0.0/public/chat.css">
  </head>
  <body>
  <div id="app" class="flex flex-column"></div>
    <script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.12.0/moment.js"></script>
    <script src="//unpkg.com/@feathersjs/client@^4.3.0/dist/feathers.js"></script>
    <script src="/socket.io/socket.io.js"></script>
    <script src="client.js"></script>
  </body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

/public/client.js

// Establish a Socket.io connection
const socket = io();

// Initialize our Feathers client application through Socket.io
// with hooks and authentication.
const client = feathers();
client.configure(feathers.socketio(socket));

// Use localStorage to store our login token
client.configure(feathers.authentication({
  storage: window.localStorage
}));

const login = async () => {
  try {
    // First try to log in with an existing JWT
    return await client.reAuthenticate();
  } catch (error) {
    // If that errors, log in with email/password
    // Here we would normally show a login page
    // to get the login information
    return await client.authenticate({
      strategy: 'local',
      email: 'hello@feathersjs.com',
      password: 'notsosecret'
    });
  }
};

const main = async () => {
  const auth = await login();

  console.log('User is authenticated', auth);

  // Log us out again
  await client.logout();
  
};

main();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

Create User

# 17 - Feathers Guide - Add Github OAuth

Go to Github OAuth and register a new application.

Now get the client ID and Client secrets and enter them on the default.json configuration at the config folder.

First infor the authetication oauth where to redirect to:

 "authentication": {
    "oauth": {
      "redirect": "/",
      "github": {
        "key": "1caf7f47469454352285f43563221",
        "secret": "d74683e5c2345062f3802312d7c6ee6065fd8fe8922281cf"
      }
    },
1
2
3
4
5
6
7
8

Now let's hide the keys:

npm i dotenv

then create a .env file at the root of the app with the keys:

GITHUB_CLIENT_ID=1caf7f47469454352285f43563221
GITHUB_CLIENT_SECRET=d74683e5c2345062f3802312d7c6ee6065fd8fe8922281cf
1
2
 "authentication": {
    "oauth": {
      "redirect": "/",
      "github": {
        "key": "GITHUB_CLIENT_ID",
        "secret": "GITHUB_CLIENT_SECRET"
      }
    },
1
2
3
4
5
6
7
8

make sure that the .gitignore file has .env in it.

and create a .env.sample file with the style of the .env file and no secrete so somebody can see how you set the .env file. This will be committed.

.env.sample

GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
1
2

The last thing is to bring in the .env file

add this to the app.js file. It has to be included before the app.configure(configuration()); line.

const dotenv = require('dotenv'); dotenv.config();

Now we are setting upp the GitHub strategy in authentication.js

authentication.js before the changes:

const { AuthenticationService, JWTStrategy } = require('@feathersjs/authentication');
const { LocalStrategy } = require('@feathersjs/authentication-local');
const { expressOauth } = require('@feathersjs/authentication-oauth');

module.exports = app => {
  const authentication = new AuthenticationService(app);

  authentication.register('jwt', new JWTStrategy());
  authentication.register('local', new LocalStrategy());

  app.use('/authentication', authentication);
  app.configure(expressOauth());
};
1
2
3
4
5
6
7
8
9
10
11
12
13

The GithubStrategy class will extend the OAuthStrategy

authentication.js after the changes:

const { AuthenticationService, JWTStrategy } = require('@feathersjs/authentication');
const { LocalStrategy } = require('@feathersjs/authentication-local');
const { expressOauth, OAuthStrategy } = require('@feathersjs/authentication-oauth');

class GithubStrategy extends OAuthStrategy {
  async getEntityData(profile) {
    const baseData = await super.getEntityData(profile);

    return {
      ...baseData,
      email: profile.email
    };
  }
}


module.exports = app => {
  const authentication = new AuthenticationService(app);

  authentication.register('jwt', new JWTStrategy());
  authentication.register('local', new LocalStrategy());
  authentication.register('github', new GithubStrategy());

  app.use('/authentication', authentication);
  app.configure(expressOauth());
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

Now visit: http://localhost:3030/oauth/github to authenticate with github.

# 18 - Feathers Guide - Chat Frontend

first let's do the buttons and link to oauth/github on the github login.

...
const loginHTML = `
<main class="login container">
    <div class="row">
        <div class="col-12 col-6-tablet push-3-tablet text-center heading">
            <h1 class="font-100">Log in or signup</h1>
        </div>
    </div>
    <div class="row">
        <div class="col-12 col-6-tablet push-3-tablet col-4-desktop push-4-desktop">
            <form class="form">
              <fieldset>
                <input class="block" type="email" name="email" placeholder="email">
              </fieldset>
              <fieldset>
                <input class="block" type="password" name="password" placeholder="password">
              </fieldset>
              <button type="button" id="login" class="button button-primary block login">
                Log in
              </button>
              <button type="button" id="signup" class="button button-primary block singuplogin">
                Sign up and Log in
              </button>
              <a class="button button-primary block github" href="/oauth/github">
                Login with Github
              </a>
            </form>
        </div>
    </div>

</main>
`;

const showLogin = () => {
  document.getElementById('app').innerHTML = loginHTML;
};

showLogin();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

Now we create a reusable helper function to listen to clicks. It will create and ev event that will return the closest target.

Now we get credentials if the closest is signup and log the credentials

const getCredentials = () => {
  const user = {
    email: document.querySelector('[name="email"]').value,
    password: document.querySelector('[name="password"]').value,
  };

  return user;
}

const addEventListener = (selector, event, handler) => {
  document.addEventListener(event, async ev => {
    if (ev.target.closest(selector)) {
      handler(ev);
    }
  });
};

addEventListener('#signup', 'click', async () => {
  const credentials = getCredentials();

  console.log(credentials);
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

Outcome of the Sing up and login button is an email and password.

{email: 'thiago@feathers.com', password: 'senhasenha'}
email: "thiago@feathers.com"
password: "senhasenha"
[[Prototype]]: Object
1
2
3
4

Now we are sending this to the backend. We will call a feathers service to create this user.

also we will check if there was an error and reload the login page. If these is an error we will show on the login function.

// Establish a Socket.io connection
const socket = io();

// Initialize our Feathers client application through Socket.io
// with hooks and authentication.
const client = feathers();
client.configure(feathers.socketio(socket));

// Use localStorage to store our login token
client.configure(feathers.authentication({
  storage: window.localStorage
}));

const login = async (credentials) => {
  try {
    if (!credentials) {
      // First try to log in with an existing JWT
      await client.reAuthenticate();
    } else {
      await client.authenticate({
        strategy: 'local',
        ...credentials,
      });
    }

    // If they are successful logged in we show chat msgs
    console.log('signup / login success! go to chat window')
  } catch (error) {
    //if there is an error we show the login page
    showLogin(error);
  }
};

const main = async () => {
  const auth = await login();

  console.log('User is authenticated', auth);

  // Log us out again
  // await client.logout();
};

main();


const loginHTML = `
<main class="login container">
    <div class="row">
        <div class="col-12 col-6-tablet push-3-tablet text-center heading">
            <h1 class="font-100">Log in or signup</h1>
        </div>
    </div>
    <div class="row">
        <div class="col-12 col-6-tablet push-3-tablet col-4-desktop push-4-desktop">
            <form class="form">
              <fieldset>
                <input class="block" type="email" name="email" placeholder="email">
              </fieldset>
              <fieldset>
                <input class="block" type="password" name="password" placeholder="password">
              </fieldset>
              <button type="button" id="login" class="button button-primary block signup">
                Log in
              </button>
              <button type="button" id="signup" class="button button-primary block signup">
                Sign up and log in
              </button>
              <a class="button button-primary block" href="/oauth/github">
                Login with Github
              </a>
            </form>
        </div>
    </div>

</main>
`;

const showLogin = (error) => {
  if (document.querySelectorAll('.login').length && error) {
    document.querySelector('.heading').insertAdjacentHTML('beforeend', '<p>There was an error: ${error.message}</p>');
  } else {
    document.getElementById('app').innerHTML = loginHTML;
  }
};

showLogin();

const getCredentials = () => {
  const user = {
    email: document.querySelector('[name="email"]').value,
    password: document.querySelector('[name="password"]').value,
  };

  return user;
}

const addEventListener = (selector, event, handler) => {
  document.addEventListener(event, async ev => {
    if (ev.target.closest(selector)) {
      handler(ev);
    }
  });
};

addEventListener('#signup', 'click', async () => {
  const credentials = getCredentials();

  await client.service('users').create(credentials);

  await login(credentials);
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111

Create User

# Code the chat window

// Establish a Socket.io connection
const socket = io();

// Initialize our Feathers client application through Socket.io
// with hooks and authentication.
const client = feathers();
client.configure(feathers.socketio(socket));

// Use localStorage to store our login token
client.configure(feathers.authentication({
  storage: window.localStorage
}));

const login = async (credentials) => {
  try {
    if (!credentials) {
      // First try to log in with an existing JWT
      await client.reAuthenticate();
    } else {
      await client.authenticate({
        strategy: 'local',
        ...credentials,
      });
    }

    // If they are successful logged in we show chat msgs
    // console.log('signup / login success! go to chat window')
    showChat();
  } catch (error) {
    //if there is an error we show the login page
    showLogin(error);
  }
};

const main = async () => {
  const auth = await login();

  console.log('User is authenticated', auth);

  // Log us out again
  // await client.logout();
};

main();


const loginHTML = `
<main class="login container">
    <div class="row">
        <div class="col-12 col-6-tablet push-3-tablet text-center heading">
            <h1 class="font-100">Log in or signup</h1>
        </div>
    </div>
    <div class="row">
        <div class="col-12 col-6-tablet push-3-tablet col-4-desktop push-4-desktop">
            <form class="form">
              <fieldset>
                <input class="block" type="email" name="email" placeholder="email">
              </fieldset>
              <fieldset>
                <input class="block" type="password" name="password" placeholder="password">
              </fieldset>
              <button type="button" id="login" class="button button-primary block signup">
                Log in
              </button>
              <button type="button" id="signup" class="button button-primary block signup">
                Sign up and log in
              </button>
              <a class="button button-primary block" href="/oauth/github">
                Login with Github
              </a>
            </form>
        </div>
    </div>

</main>
`;

const chatHTML = `
  <main class="flex flex-column">
    <header class="title-bar flex flex-row flex-center">
        <div class="title-wrapper block center-element">
            <img class="logo" src="https://skinnyrobot.co.nz/images/logo.svg" alt="Skinny Robot Logo">
            <span class="title">Chat</span>
        </div>
    </header>
  </main>
`;


const showLogin = (error) => {
  if (document.querySelectorAll('.login').length && error) {
    document.querySelector('.heading').insertAdjacentHTML('beforeend', '<p>There was an error: ${error.message}</p>');
  } else {
    document.getElementById('app').innerHTML = loginHTML;
  }
};

const showChat = () => {
  document.getElementById('app').innerHTML = chatHTML;
}

showLogin();

const getCredentials = () => {
  const user = {
    email: document.querySelector('[name="email"]').value,
    password: document.querySelector('[name="password"]').value,
  };

  return user;
}

const addEventListener = (selector, event, handler) => {
  document.addEventListener(event, async ev => {
    if (ev.target.closest(selector)) {
      handler(ev);
    }
  });
};

addEventListener('#signup', 'click', async () => {
  const credentials = getCredentials();

  await client.service('users').create(credentials);

  await login(credentials);
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128

# Show the messages

const chatHTML = `
  <main class="flex flex-column">
    <header class="title-bar flex flex-row flex-center">
        <div class="title-wrapper block center-element">
            <img class="logo" src="https://skinnyrobot.co.nz/images/logo.svg" alt="Skinny Robot Logo">
            <span class="title">Chat</span>
        </div>
    </header>

    <div class="flex flex-row flex-1 clear">
        <aside class="sidebar col col-3 flex flex-column flex-space-between">
            <header class="flex flex-row flex-center">
                <h4 class="font-300 text-center">
                    <span class="font-600 online-count">0</span> users
                </h4>
            </header>
            <ul class="flex flex-column flex-1 list-unstyled user-list"></ul>
            <footer class="flex flex-row flex-center">
              <a href="#" id="logout" class="button button-primary">
                Sign Out
              </a>
            </footer>
        </aside>
    </div>
  </main>
`;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# Implement sign out

addEventListener('#logout', 'click', async () => {
  await client.logout('users');

  document.getElementById('app').innerHTML = loginHTML;
})
1
2
3
4
5

# Implement login

addEventListener('#login', 'click', async () => {
  const user = getCredentials();

  await login(user);
})
1
2
3
4
5

# Implementing showing all the messages and where to enter the messages

<div class="flex flex-column col col-9">
  <main class="chat flex flex-row flex-1 clear"></main>
  <form class="flex flex-row flex-space-between" id="send-message">
      <input type="text" name="text" class="flex flex-1">
      <button class="button-primary">Send</button>
  </form>
</div>
1
2
3
4
5
6
7

# Getting the messages from the backend

// Establish a Socket.io connection
const socket = io();

// Initialize our Feathers client application through Socket.io
// with hooks and authentication.
const client = feathers();
client.configure(feathers.socketio(socket));

// Use localStorage to store our login token
client.configure(feathers.authentication({
  storage: window.localStorage
}));

const login = async (credentials) => {
  try {
    if (!credentials) {
      // First try to log in with an existing JWT
      await client.reAuthenticate();
    } else {
      await client.authenticate({
        strategy: 'local',
        ...credentials,
      });
    }

    // If they are successful logged in we show chat msgs
    // console.log('signup / login success! go to chat window')
    showChat();
  } catch (error) {
    //if there is an error we show the login page
    showLogin(error);
  }
};

const main = async () => {
  const auth = await login();

  console.log('User is authenticated', auth);

  // Log us out again
  // await client.logout();
};

main();


const loginHTML = `
<main class="login container">
    <div class="row">
        <div class="col-12 col-6-tablet push-3-tablet text-center heading">
            <h1 class="font-100">Log in or signup</h1>
        </div>
    </div>
    <div class="row">
        <div class="col-12 col-6-tablet push-3-tablet col-4-desktop push-4-desktop">
            <form class="form">
              <fieldset>
                <input class="block" type="email" name="email" placeholder="email">
              </fieldset>
              <fieldset>
                <input class="block" type="password" name="password" placeholder="password">
              </fieldset>
              <button type="button" id="login" class="button button-primary block signup">
                Log in
              </button>
              <button type="button" id="signup" class="button button-primary block signup">
                Sign up and log in
              </button>
              <a class="button button-primary block" href="/oauth/github">
                Login with Github
              </a>
            </form>
        </div>
    </div>
</main>
`;

const chatHTML = `
<main class="flex flex-column">
  <header class="title-bar flex flex-row flex-center">
      <div class="title-wrapper block center-element">
          <img class="logo" src="https://skinnyrobot.co.nz/images/logo.svg" alt="Skinny Robot Logo">
          <span class="title">Chat</span>
      </div>
  </header>

  <div class="flex flex-row flex-1 clear">
    <aside class="sidebar col col-3 flex flex-column flex-space-between">
        <header class="flex flex-row flex-center">
            <h4 class="font-300 text-center">
                <span class="font-600 online-count">0</span> users
            </h4>
        </header>
        <ul class="flex flex-column flex-1 list-unstyled user-list"></ul>
        <footer class="flex flex-row flex-center">
          <a href="#" id="logout" class="button button-primary">
            Sign Out
          </a>
        </footer>
    </aside>

    <div class="flex flex-column col col-9">
      <main class="chat flex flex-row flex-1 clear"></main>

      <form class="flex flex-row flex-space-between" id="send-message">
          <input type="text" name="text" class="flex flex-1">
          <button class="button-primary">Send</button>
      </form>
    </div>
  </div>
</main>
`;


const showLogin = (error) => {
  if (document.querySelectorAll('.login').length && error) {
    document.querySelector('.heading').insertAdjacentHTML('beforeend', '<p>There was an error: ${error.message}</p>');
  } else {
    document.getElementById('app').innerHTML = loginHTML;
  }
};

const showChat = async () => {
  document.getElementById('app').innerHTML = chatHTML;

  const messages = await client.service('messages').find({
    query: {
      $sort: { createdAt: -1},
      $limit: 25
    }
  });

  messages.data.reverse().forEach(addMessage);

  const users = await client.service('users').find();

  users.data.forEach(addUser);
};

const addUser = user => {
  const userList = document.querySelector('.user-list');

  if (userList) {
    userList.innerHTML += `
      <li>
        <a class="block relative" href="#">
          <img src="${user.avatar}" class="avatar" alt="user avatar">
          <span class="absolute username">${user.email}</span>
        </a>
      </li>
    `;


    const userCount = document.querySelectorAll('.user-list li').length;

    document.querySelector('.online-count').innerHTML = userCount;
  }
}

const addMessage = message => {
  const { user = {}} = message;
  const chat = document.querySelector('.chat');

  const text =  message.text
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')

  //make sure that the text element exist and add to the page
  if (chat) {
    chat.innerHTML += `
    <div class="message flex flex-row">
        <img src="${user.avatar}" alt="${user.email}" class="avatar">
        <div class="message-wrapper">
            <p class="message-header">
                <span class="username font-600">${user.email}</span>
                <span class="sent-date font-300">${moment(message.createdAt).format('MM Do, hh:mm:ss')}</span>
            </p>
            <p class="message=content font-300">${text}</p>
        </div>
    </div>
    `;

    chat.scrollTop = chat.scrollHeight - chat.scrollHeight;
  }
}

const getCredentials = () => {
  const user = {
    email: document.querySelector('[name="email"]').value,
    password: document.querySelector('[name="password"]').value,
  };

  return user;
}

const addEventListener = (selector, event, handler) => {
  document.addEventListener(event, async ev => {
    if (ev.target.closest(selector)) {
      handler(ev);
    }
  });
};

addEventListener('#signup', 'click', async () => {
  const credentials = getCredentials();

  await client.service('users').create(credentials);

  await login(credentials);
})

addEventListener('#login', 'click', async () => {
  const user = getCredentials();

  await login(user);
})

addEventListener('#logout', 'click', async () => {
  await client.logout('users');

  document.getElementById('app').innerHTML = loginHTML;
})

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224

Create User

# Sending messages to the server

addEventListener('#send-message', 'submit', async ev => {
  const input = document.querySelector('[name="text"]')

  ev.preventDefault();

  //Call the feathers service to create that message on the backend
  await client.service('messages').create({
    text: input.value
  });

  input.value = '';
})
1
2
3
4
5
6
7
8
9
10
11
12

# Refreshing for new messages and new users

//refresh messages
client.service('messages').on('created', addMessage);
//refresh users
client.service('users').on('created', addUser);
1
2
3
4

# Creating the Poems Service

feathers generate service
1
? What kind of service is it? MongoDB
? What is the name of the service? poems                   
? Which path should the service be registered on? /poems
? Does the service require authentication? Yes
   create src\services\poems\poems.service.js
    force src\services\index.js              
   create src\services\poems\poems.class.js  
   create src\services\poems\poems.hooks.js  
   create test\services\poems.test.js 
1
2
3
4
5
6
7
8
9

poems.class.js















 







const { Service } = require('feathers-mongodb');

exports.Poems = class Poems extends Service {
  constructor(options, app) {
    super(options);

    app.get('mongoClient').then(db => {
      this.Model = db.collection('poems');
    });
  }

  create(data, params) {
    const {title, body} = data;
    const poemData = {
      title,
      body,
    };

    return super.create(poemData, params);
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

Include the service in the store:

// ~/store/services/poems.js
import feathersClient, {
  makeServicePlugin,
  BaseModel,
} from "~/plugins/feathers";

class Poems extends BaseModel {
  constructor(data, options) {
    super(data, options);
  }
  // Required for $FeathersVuex plugin to work after production transpile.
  static modelName = "Poems";
  // Define default properties here
  static instanceDefaults() {
    return {
      title: "",
      body: "",
    };
  }
}
const servicePath = "poems";
const servicePlugin = makeServicePlugin({
  Model: Poems,
  service: feathersClient.service(servicePath),
  servicePath,
});

// Setup the client-side Feathers hooks.
feathersClient.service(servicePath).hooks({
  before: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: [],
  },
  after: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: [],
  },
  error: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: [],
  },
});

export default servicePlugin;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59

Test with Postman:


Poems Service


Poems Service

admin page:

<template>
  <div class="wrapper">
    <div class="child">
      <h1>Administration Page</h1>
      <h2>Create Poem:</h2>
      <form class="py-4" @submit.prevent="submit">
        <v-text-field
          v-model="poem.title"
          label="Title"
          required
        ></v-text-field>
        <v-textarea v-model="poem.body" label="Body" required></v-textarea>
        <v-btn class="mr-4" type="submit"> submit </v-btn>
        <v-btn @click="clear"> clear </v-btn>
      </form>
    </div>
  </div>
</template>
<script>
import { mapActions } from "vuex";

export default {
  // middleware: ["admin"],
  data: () => ({
    poem: { title: "", body: "" },
  }),
  methods: {
    ...mapActions("poems", { createPoem: "create" }),
    addPoem: async function () {
      await this.createPoem(this.poem);
    },
    submit() {
      console.log("Title: " + this.poem.title);
      console.log("Body: " + this.poem.body);
      this.addPoem();
    },
    clear() {
      this.poem.title = "";
      this.poem.body = "";
    },
  },
};
</script>

<style>
.wrapper {
  margin: 0 auto;
  width: 80%;
  height: 100%;
  /*border: 0.5rem solid black;*/
  display: flex;
  justify-content: center;
  align-items: center;
  /*overflow: scroll;*/
}
.child {
  padding: 0.2rem;
  width: 80%;
  /*border: 0.5rem solid red;*/
}
</style>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61

# File Upload with Feathers-blob

Create the service

// Initializes the `uploads` service on path `/uploads`
const { Uploads } = require('./uploads.class');
const hooks = require('./uploads.hooks');

// feathers-blob service
const blobService = require('feathers-blob');
// Here we initialize a FileSystem storage,
// but you can use feathers-blob with any other
// storage service like AWS or Google Drive.
const fs = require('fs-blob-store');


// File storage location. Folder must be created before upload.
// Example: './uploads' will be located under feathers app top level.
const blobStorage = fs('./uploads');


module.exports = function (app) {
  const options = {
    paginate: app.get('paginate')
  };

  // Initialize our service with any options it requires
  app.use('/uploads',  blobService({ Model: blobStorage}));

  // Get our initialized service so that we can register hooks
  const service = app.service('uploads');

  service.hooks(hooks);
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30

# Custom Service with Docxtemplater

feathers generate service
1

then choose custom service, and give a name, in this case was vertical-report

now we can just rewrite the create function:


 
 
 
 
 
 

















 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 












/* eslint-disable no-unused-vars */
const Docxtemplater = require('docxtemplater');
const path = require("path");
const fs = require('fs');
const PizZip = require('pizzip');
const saveAs = require('pizzip');
var http = require('http');


exports.VerticalReport = class VerticalReport {
  constructor (options) {
    this.options = options || {};
  }

  async find (params) {
    return [];
  }

  async get (id, params) {
    return {
      id, text: `A new message with ID: ${id}!`
    };
  }

  async create (data, params) {
    if (Array.isArray(data)) {
      return Promise.all(data.map(current => this.create(current, params)));
    }
    const ext = '.docx'
    const inputPath = path.join(__dirname, 'ATC-IB-NDT-011.docx')
    const outputPath = path.join(__dirname, `${data.filename}${ext}`)

    // Read file
    // const docxBuf = await fs.readFileSync(inputPath)
    const docxBuf = fs.readFileSync(inputPath, 'binary')

    const zip = new PizZip(docxBuf)
    const doc = new Docxtemplater(zip, {
      paragraphLoop: true,
      linebreaks: true,
    })

    // Render the document (Replace {first_name} by John, {last_name} by Doe, ...)
    doc.render(data)

    const report = doc.getZip().generate({
      type: 'nodebuffer',
      compression: 'DEFLATE',
    })

    await fs.writeFileSync(outputPath, report)

    return {data: report};
  }

  async update (id, data, params) {
    return data;
  }

  async patch (id, data, params) {
    return data;
  }

  async remove (id, params) {
    return { id };
  }
};

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68

Include the service on the store of the client.

// ~/store/services/users.js
import feathersClient, {
  makeServicePlugin,
  BaseModel,
} from "~/plugins/feathers";

class Vertical extends BaseModel {
  constructor(data, options) {
    super(data, options);
  }
  // Required for $FeathersVuex plugin to work after production transpile.
  static modelName = "Vertical";
  // Define default properties here
  static instanceDefaults() {
    return {
      data: null,
    };
  }
}
const servicePath = "vertical-report";
const servicePlugin = makeServicePlugin({
  Model: Vertical,
  service: feathersClient.service(servicePath),
  servicePath,
});

// Setup the client-side Feathers hooks.
feathersClient.service(servicePath).hooks({
  before: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: [],
  },
  after: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: [],
  },
  error: {
    all: [],
    find: [],
    get: [],
    create: [],
    update: [],
    patch: [],
    remove: [],
  },
});

export default servicePlugin;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58

And prepare the frontend to download the file

methods: {
    ...mapActions("vertical-report", { createVertical: "create" }),
    async download(response) {
      console.log(response);
      const url = window.URL.createObjectURL(
        new Blob([response], {
          type: "blob",
          mimeType:
            "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
        })
      );
      console.log(response.data);
      const link = document.createElement("a");
      link.href = url;
      link.download = "Vertical-Linearity-Report.docx";
      link.click();
      window.URL.revokeObjectURL(url);
    },
    verticalReport: async function () {
      return this.createVertical(this.verticalCalibrationValues).then(
        (response) => {
          console.log(response.data);
          this.download(response.data);
        }
      );
    },
  },
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27