$ npm install @feathersjs/cli -g
$ mkdir feathers-chat
$ feathers generate app # 创建APP
? Project name feathers-chat
? Description A Feathers chat application
? 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
create package.json
create config/default.json
create LICENSE
create public/favicon.ico
create public/index.html
create .editorconfig
create .eslintrc.json
create src/app.hooks.js
create src/channels.js
create src/hooks/logger.js
create src/index.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 config/production.json
$ npm start
$ npm test
Generating a service :
$ feathers generate service
? What kind of service is it? NeDB
? What is the name of the service? messages
? Which path should the service be registered on? /messages
? What is the database connection string? nedb://../data
force config/default.json
$ npm start
http://localhost:3030/messages
$ curl 'http://localhost:3030/messages/' -H 'Content-Type: application/json' --data-binary '{ "name": "Curler", "text": "Hello from the command line!" }'
Generating authentication :
$ feathers generate authentication
? What authentication providers do you want to use? Other PassportJS strategies
not in this list can still be configured manually. Username + Password (Local),
Auth0, Google, Facebook, GitHub
? What is the name of the user (entity) service? users
? What kind of service is it? NeDB
Creating a user and logging in :
$ curl 'http://localhost:3030/users/' -H 'Content-Type: application/json' --data-binary '{ "email": "feathers@example.com", "password": "secret" }'
{"email":"feathers@example.com","_id":"fuF1BNK5k0ieuQOZ"}
$ curl 'http://localhost:3030/authentication/' -H 'Content-Type: application/json' --data-binary '{ "strategy": "local", "email": "feathers@example.com", "password": "secret" }'
{"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6ImFjY2VzcyJ9.eyJ1c2VySWQiOiJmdUYxQk5LNWswaWV1UU9aIiwiaWF0IjoxNTE3NTc1NjkyLCJleHAiOjE1MTc2NjIwOTIsImF1ZCI6Imh0dHBzOi8veW91cmRvbWFpbi5jb20iLCJpc3MiOiJmZWF0aGVycyIsInN1YiI6ImFub255bW91cyIsImp0aSI6IjM1M2Q0MjVhLTcxOGItNGI5Yy04ZmE4LTA2ZTY3MWJmZTdmMyJ9.feGAxy4aeIUeEMvVygt6DsMFkqcvLOMF4kriQpeoIDs"}
Securing the messages service : update src/services/messages/messages.hooks.js
const { authenticate } = require('@feathersjs/authentication').hooks;
module.exports = {
before: {
all: [ authenticate('jwt') ],
find: [],
Securing real-time events : make sure that real-time service events are only sent to connections that are allowed to see them - for example when users join a specific chat room or one-to-one messages.
src/channels.js
sanitize the input we get from the client and add additional information.
Sanitizing new message :
$ 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
Update src/hooks/process-message.js
Adding a user avatar : add a link to the gravatar images of the user's email address.
$ feathers generate hook
? What is the name of the hook? gravatar
? What kind of hook should it be? before
? What service(s) should this hook be for (select none to add it yourself)?
users
? What methods should the hook be for (select none to add it yourself)? create
identical src/hooks/gravatar.js
identical test/hooks/gravatar.test.js
Update src/hooks/gravatar.js
Populating the message sender : In order to show the right user information we want to include that information in our messages.
$ 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
Update src/hooks/populate-user.js
We now have a complete API to send and retrieve messages including authentication.
Feathers works great in the browser and comes with client services that allow to easily connect to a Feathers server.
public/index.html
<html>
<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>Vanilla JavaScript Feathers Chat</title>
<link rel="shortcut icon" href="favicon.ico">
<link rel="stylesheet" href="//cdn.rawgit.com/feathersjs/feathers-chat/v0.2.0/public/base.css">
<link rel="stylesheet" href="//cdn.rawgit.com/feathersjs/feathers-chat/v0.2.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@^3.0.0/dist/feathers.js"></script>
</script>
<script src="/socket.io/socket.io.js"></script>
<script src="app.js"></script>
</body>
</html>
public/app.js
Base and user/message list HTML :
Displaying the login/signup or chat page :
Login and signup :
Real-time and sending message :
// 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
}));
// Login screen
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>
</form>
</div>
</div>
</main>`;
// Chat base HTML (without user list and 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="http://feathersjs.com/img/feathers-logo-wide.png"
alt="Feathers 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-column 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" type="submit">Send</button>
</form>
</div>
</div>
</main>`;
// Add a new user to the list
const addUser = user => {
const userList = document.querySelector('.user-list');
if (userList) {
// Add the user to the list
userList.insertAdjacentHTML('beforeend', `<li>
<a class="block relative" href="#">
<img src="${user.avatar}" alt="" class="avatar">
<span class="absolute username">${user.email}</span>
</a>
</li>`);
// Update the number of users
const userCount = document.querySelectorAll('.user-list li').length;
document.querySelector('.online-count').innerHTML = userCount;
}
};
// Renders a new message and finds the user that belongs to the message
const addMessage = message => {
// Find the user belonging to this message or use the anonymous user if not found
const { user = {} } = message;
const chat = document.querySelector('.chat');
// Escape HTML
const text = message.text
.replace(/&/g, '&')
.replace(/</g, '<').replace(/>/g, '>');
if (chat) {
chat.insertAdjacentHTML( 'beforeend', `<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('MMM Do, hh:mm:ss')}</span>
</p>
<p class="message-content font-300">${text}</p>
</div>
</div>` );
chat.scrollTop = chat.scrollHeight - chat.clientHeight;
}
};
// Show the login page
const showLogin = (error = {}) => {
if (document.querySelectorAll('.login').length) {
document.querySelector('.heading').insertAdjacentHTML('beforeend', `<p>There was an error: ${error.message}</p>`);
} else {
document.getElementById('app').innerHTML = loginHTML;
}
};
// Shows the chat page
const showChat = async () => {
document.getElementById('app').innerHTML = chatHTML;
// Find the latest 25 messages
const messages = await client.service('messages').find({
query: {
$sort: { createdAt: -1 },
$limit: 25
}
});
// We want to show the newest message last
messages.data.reverse().forEach(addMessage);
// Find all users
const users = await client.service('users').find();
users.data.forEach(addUser);
};
// Retrieve email/password object from the login/signup page
const getCredentials = () => {
const user = {
email: document.querySelector('[name="email"]').value,
password: document.querySelector('[name="password"]').value
};
return user;
};
// Log in either using the given email/password or the token from storage
const login = async credentials => {
try {
if (!credentials) {
// Try to authenticate using the JWT from localStorage
await client.authenticate();
} else {
// If we get login information, add the strategy we want to use for login
const payload = object.assign({ strategy: 'local' }, credentials );
await client.authenticate(payload);
}
// If successful, show the chat page
showChat();
} catch(error) {
// If we got an error, show the login page
showLogin(error);
}
};
document.addEventListener('click', async ev => {
switch(ev.target.id) {
case 'signup' : {
// For signup, create a new user and then log them in
const credentials = getCredentials();
// First create the user
await client.service('users').create(credentials);
// If successful log them in
await login(credentials);
break;
}
case 'login' : {
const user = getCredentials();
await login(user);
break;
}
case 'logout' : {
await client.logout();
document.getElementById('app').innerHTML = loginHTML;
break;
}
}
});
// send new message and make the user and message list update in real-time
document.addEventListener('submit', async ev => {
if (ev.target.id === 'send-message') {
// This is the message text input field
const input = document.querySelector('[name="text"]');
ev.preventDefault();
// Create a new message and then clear the input field
await client.service('messages').create({
text: input.value
});
input.value = '';
}
});
// Listen to created events and add the new message in real-time
client.service('messages').on('created', addMessage);
// We will also see when new users get created in real-time
client.service('users').on('created', addUser);
login();
test