# Feathers Js
FeathersJS Guide - Coding Garden
Feathers is a framework for real-time applications and REST APIs.
# Quick guide
npm i @feathersjs/feathers
npm i @feathersjs/express @feathersjs/socketio
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
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();
2
3
ES6 Modules
import feathers from '@feathersjs/feathers';
const app = feathers();
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);
}
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();
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Syntax
async function name([param[, param[, ... param]]]) {
statements
}
2
3
# Promise
Things we may want to do:
app.service('messages').create({
text: 'Hello Feathers'
}).then(created => console.log(created));
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]
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
cd feathers-basics/
Then initiate a package json inside the folder with npm init -y
npm init -y
{
"name": "feathers-basics",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
2
3
4
5
6
7
8
9
10
11
12
and open it with code:
code .
Now install the FeathersJS dependencies:
npm i @feathersjs/feathers
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();
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
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' } ]
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
Now that we have those dependencies we can bring them in:
const express = require('@feathersjs/express');
const socketio = require('@feathersjs/socketio');
2
we also move the creation of the app below the message service
const app = feathers();
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());
# 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());
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 }));
Now a static server that will serve up the clients:
app.use(express.static(__dirname));
# 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())
And with this one we have exposed the feather application over sockets
app.configure(socketio());
A important change:
app.use('messages', new MessageService())
will now have a slash / , This will be like registering a route in Express.
app.use('/messages', new MessageService())
Last thing to do will be to use the error service.
app.use(express.errorHandler());
# 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);
})
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'));
Last thing will be to listen on port 3030
app.listen(3030).on('listening', () => {
console.log('Feathers server listening on localhost:3030');
});
2
3
To see that in action:
app.service('messages').create({
text: 'Hello world from the server!'
});
2
3
Now we can start the application:
Thiago Souto@MSI MINGW64 ~/Desktop/feathers-basics
$ node app.js
Feathers server listening on localhost:3030
2
3
by visiting http://localhost:3030/messages we get:
[{"id":0,"text":"Hello world from the server!"}]
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!'
});
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!"
}
]
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>
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
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.
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
When used as a REST API, incoming requests get mapped automatically to their corresponding service method.
Methods that don't modify:
Methods that modify:
Feather have many Database adapters.
# Generating a Feathers Service
feathers generate service
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
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');
});
}
};
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);
}
};
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.
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]
}
});
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')]
}
});
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: []
}
};
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
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
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;
};
};
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
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;
};
};
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.
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
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"
}
2
3
4
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"
}
2
3
4
5
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
And with the token, and set a Authorizatiopn header with a Bearer and the token, it will be:
Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6ImFjY2VzcyJ9.eyJpYXQiOjE2NDUxNzMzMDMsImV4cCI6MTY0NTI1OTcwMywiYXVkIjoiaHR0cHM6Ly95b3VyZG9tYWluLmNvbSIsImlzcyI6ImZlYXRoZXJzIiwic3ViIjoiNjIwZjU4ZmUxYjA3Y2E3ODRhOTA3ZWU3IiwianRpIjoiMmQzN2IzYmItMGFmOC00MWMxLWEwNWMtYzg3M2NhYzA4N2YwIn0.WkbZLwQWFdN00jrNU6FHMhI5lGsdHbMmfUkiIgenmOg
# 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>
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();
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
# 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"
}
},
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
2
"authentication": {
"oauth": {
"redirect": "/",
"github": {
"key": "GITHUB_CLIENT_ID",
"secret": "GITHUB_CLIENT_SECRET"
}
},
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=
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());
};
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());
};
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();
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);
})
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
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);
})
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
# 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);
})
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>
`;
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;
})
2
3
4
5
# Implement login
addEventListener('#login', 'click', async () => {
const user = getCredentials();
await login(user);
})
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>
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, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
//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;
})
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
# 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 = '';
})
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);
2
3
4
# Creating the Poems Service
feathers generate service
? 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
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);
}
};
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;
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:
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>
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);
};
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
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 };
}
};
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;
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);
}
);
},
},
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