https://docs.feathersjs.com/guides/basics/readme.html
1.1、The Basics
Setting up:安装
$ mkdir feathers-basics
$ npm init --yes #默认初始化
$ npm install @feathersjs/feathers --save #安装feathers
$ node app.js
const feathers = require('@feathersjs/feathers');
const app = feathers();
// Register a simple todo service that return the name and a text
app.use('todos', {
async get(name) {
return {
name,
text: 'You have to do ${name}'
};
}
});
// A function that gets and logs a todo from the service
async function getTodo(name) {
const service = app.service('todos');
// Call the 'get' method with a name
const todo = await service.get(name);
// Log the todo we got back
console.log(todo);
}
getTodo('dishes');
$ node app.js
{ name: 'dishes', text: 'You have to do ${name}' }
In the browser
$ mkdir public
$ npm install http-server -g
$ http-server public/
$ subl public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Feathers Basics</title>
</head>
<body>
<h1>Welcome to Feathers</h1>
<p>Open up the console in your browser.</p>
<script type="text/javascript" src="//unpkg.com/@feathersjs/client@^3.0.0/dist/feathers.js"></script>
<script src="client.js"></script>
</body>
</html>
$ subl public/client.js
const app = feathers();
// Register a simple todo service that return the name and a text
app.use('todos', {
async get(name) {
// Return an object in the form of { name, text }
return {
name,
text: `You have to do ${name}`
};
}
});
// A function that gets and logs a todo from the service
async function logTodo(name) {
// Get the service we registered above
const service = app.service('todos');
// Call the `get` method with a name
const todo = await service.get(name);
// Log the todo we got back
console.log(todo);
}
logTodo('dishes');
http://127.0.0.1:8080/
Services:the heart of every Feathers application and JavaScript objects or instances of a class that implement certain methods. Services provide a uniform, protocol independent interface for how to interact with any kind of data like: ...
Protocol independent means that to a Feathers service it does not matter if it has been called internally, through a REST API or websockets.
Service methods are CRUD methods that a service object can implement.
A messages services : app.js
const feathers = require('@feathersjs/feathers');
class Messages {
constructor() {
this.messages = [];
this.currentId = 0;
}
async find(params) {
// Return the list of all messages
return this.messages;
}
async get(id, params) {
//Find the message by id
const message = this.messages.find(message => message.id === parseInt(id, 10));
// Throw an error if it wasn't found
if (!message) {
throw new Error('Message with id ${id} not found');
}
// Otherwise return the message
return message;
}
async create(data, params) {
// Create a new object with the original data and an id
// taken from the incrementing 'currentId' counter
const message = Object.assign({
id : ++this.currentId
}, data);
this.messages.push(message);
return message;
}
async path(id, data, params) {
// Get the existing message. Will throw an error if not found
const message = await this.get(id);
// Merge the existing message with the new data
// and return the result
return Object.assign(message, data);
}
async remove(id, params) {
const message = await this.get(id);
const index = this.messages.indexOf(message);
this.messages.splice(index, 1);
return message;
}
}
const app = feathers();
//启用Services
app.use('messages', new Messages());
//Using Services
async function processMessages() {
await app.service('messages').create({
text: 'First message'
});
await app.service('messages').create({
text: 'Second message'
});
const messageList = await app.service('messages').find();
console.log('Available messages', messageList);
}
processMessages();
$ node app.js
Available messages [ { id: 1, text: 'First message' },
{ id: 2, text: 'Second message' } ]
Service events : Events can be listened to with app.service('messages').on('eventName', data => {}).
//Using Services
async function processMessages() {
app.service('messages').on('created', message => {
console.log('Created a new message', message);
});
app.service('messages').on('removed', message => {
console.log('Deleted message', message);
});
await app.service('messages').create({
text: 'First message'
});
const lastMessage = await app.service('messages').create({
text: 'Second message'
});
await app.service('messages').remove(lastMessage.id);
const messageList = await app.service('messages').find();
console.log('Available messages', messageList);
}
$ node app.js
Created a new message { id: 1, text: 'First message' }
Created a new message { id: 2, text: 'Second message' }
Deleted message { id: 2, text: 'Second message' }
Available messages [ { id: 1, text: 'First message' } ]
Hooks:pluggable middleware functions that can be registered before, after or on errors of a service method. You can register a single hook function or create a chain of them to create complex work-flows.
Hookes are commonly used to handle things like validation, authorization, logging, populating related entities, sending notifications and more.
const messagesHooks = {
before: {
all: [ hook01() ],
find: [ hook11() ],
get: [ hook21() ],
create: [ hook31(), hook32() ],
update: [ hook41() ],
patch: [ hook51() ],
remove: [ hook61() ],
},
after: {
all: [ hook05() ],
find: [ hook15(), hook16() ],
create: [ hook35() ],
update: [ hook45() ],
patch: [ hook55() ],
remove: [ hook65() ],
}
};
app.service('messages').hooks(messagesHooks);
If a hook throws an error, all following hooks will be skipped and the error will be returned to the user.
$ npm install @feathersjs/errors --save
const { BadRequest} = require('@feathersjs/errors');
const validate = async context => {
const { data } = context;
// Check if there is 'text' property
if (!data.text) {
throw new BadRequest('Message text must exist');
}
// Check if it is a string and not just whitespace
if (typeof data.text !== 'string' || data.text.trim() === '') {
throw new BadRequest('Message text is invalid');
}
//
context.data = {
text: data.text.toString(),
}
return context;
};
app.service('messages').hooks({
before: {
create: validate,
update: validate,
patch: validate
}
});
A good use for application hooks is to log my service method call error.
REST APIs : Currently Feathers officially has three transports :
- HTTP REST via Express for exposing services through a JSON REST API
- Socket.io for connecting to services through websockets and also receiving real-time service events
- Primus an alternative to Socket.io supporting several websocket protocols which also supports real-time events
What the Feathers REST transport essentially does is to automatically map our existing service methods to those endpoints.
Express integration : You can use Feathers functionality like services but also any existing Express middleware.
$ npm install @feathersjs/express --save
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
class Messages {
constructor() {
this.messages = [];
this.currentId = 0;
}
async find(params) {
// Return the list of all messages
return this.messages;
}
async get(id, params) {
//Find the message by id
const message = this.messages.find(message => message.id === parseInt(id, 10));
// Throw an error if it wasn't found
if (!message) {
throw new Error('Message with id ${id} not found');
}
// Otherwise return the message
return message;
}
async create(data, params) {
// Create a new object with the original data and an id
// taken from the incrementing 'currentId' counter
const message = Object.assign({
id : ++this.currentId
}, data);
this.messages.push(message);
return message;
}
async path(id, data, params) {
// Get the existing message. Will throw an error if not found
const message = await this.get(id);
// Merge the existing message with the new data
// and return the result
return Object.assign(message, data);
}
async remove(id, params) {
const message = await this.get(id);
const index = this.messages.indexOf(message);
this.messages.splice(index, 1);
return message;
}
}
// const app = feathers();
//启用Services
//app.use('messages', new Messages());
// This creates an app that is both, an Express and Feathers app
const app = express(feathers());
// Turn on JSON body parsing for REST services
app.use(express.json());
// Turn on URL-encoded body parsing for REST services
app.use(express.urlencoded({ extended: true }));
// Set up REST transport using Express
app.configure(express.rest())
app.use('messages', new Messages()); // --->
// Set up an error handler that gives us nicer errors
app.use(express.errorHandler());
const server = app.listen(3030);
app.service('messages').create({
text: 'Hello from the server'
});
server.on('listening', () => console.log('Feathers REST API started at localhost:3030'));
$ node app.js
Feathers REST API started at localhost:3030
http://localhost:3030/messages
$ curl 'http://localhost:3030/messages/' -H 'Content-Type: application/json' --data-binary '{ "text": "Hello from the command line!" }'
$ curl -X "DELETE" http://localhost:3030/messages/1
Databases : Feathers has a collection of pre-built services for different databases.
Tip: Each one of the linked adapters has a complete REST API example in their readme.
$ npm install feathers-memory --save
app.use('messages', memory({
paginate: {
default: 10,
max: 25
}
}));
Querying : all database adapters support a common way of querying the data in a find method call using params.query.
async function createAndFind() {
const messages = app.service('messages');
for (let counter=0; counter<100; counter++) {
await messages.create({
counter,
message: 'Message number ${counter}'
});
}
// we show 10 entries by default, By skipping 10 we go to page 2
const page2 = await messages.find({
query: { $skip: 10 }
});
console.log('Page number 2', page2);
// Show 20 items per page
const largePage = await messages.find({
query: { $limit: 20 }
});
console.log('20 items', largePage);
// Find the first 10 items with counter greater 50 and less than 70
const counterList = await messages.find({
query: {
counter: { $gt: 50, $lt: 70 }
}
});
console.log('Counter greater 50 and less than 70', counterList);
// Find all entries with text "message number 20"
const message20 = await messages.find({
query: {
message: 'Message number 20'
}
});
console.log('Entries with text "Message number 20"', message20);
}
createAndFind();
As a REST API : Using a database adapter instead will make our app.js a lot shorter.
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
const memory = require('feathers-memory');
// This creates an app that is both, an Express and Feathers app
const app = express(feathers());
// Turn on JSON body parsing for REST services
app.use(express.json());
// Turn on URL-encoded body parsing for REST services
app.use(express.urlencoded({ extended: true }));
// Set up REST transport using Express
app.configure(express.rest())
// Set up an error handler that gives us nicer errors
app.use(express.errorHandler());
app.use('messages', memory({
paginate: {
default: 10,
max: 25
}
}));
const server = app.listen(3030);
app.service('messages').create({
text: 'Hello from the server'
});
server.on('listening', () => console.log('Feathers REST API started at localhost:3030'));
http://localhost:3030/messages?$limit=2
Real-Time APIs : means that those events are also published to connected clients so that they can react accordingly, e.g. update their UI.
Important: The REST transport does not support real-time updates. Since socket transport also allow to call service methods and generally perform better, we recommend using a real-time transport whenever possible.
$ npm install @feathersjs/socketio --save
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
const socketio = require('@feathersjs/socketio');
const app = express(feathers());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Set up REST transport using Express
app.configure(express.rest());
app.use(express.errorHandler());
// Configure the Socket.io transport
app.configure(socketio);
app.listen(3030);
Channels : determine which real-time events should be sent to which client. For example, we want to only send messages to authenticated users or users in the same room. For this example we will just enable real-time functionality for all connections
const feathers = require('@feathersjs/feathers');
const express = require('@feathersjs/express');
const socketio = require('@feathersjs/socketio');
const memory = require('feathers-memory');
const app = express(feathers());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Set up REST transport using Express
app.configure(express.rest());
// Configure the Socket.io transport
app.configure(socketio);
// On any real-time connection, add it to the 'everybody' channel
app.on('connection', connection => app.channel('everybody').join(connection));
// Publish all events to the 'everybody' channel
app.publish( () => app.channel('everybody') );
// Initialize the messages service
app.use('messages', memory({
paginate: {
default: 10,
max: 25
}
}));
app.use(express.errorHandler());
const server = app.listen(3030);
server.on('listening', ()=> console.log('Feathers API started at localhost:3030'));
public/client.js
const app = feathers();
app.use('messages', memory({
paginate: {
default: 10,
max: 25
}
}));
const socket = io('http://localhost:3030');
socket.on('messages created', message =>
console.log('Someone created a message', message)
);
socket.emit('create', 'messages', {
text: 'Hello from socket'
}, (error, result) => {
if (error) throw error
socket.emit('find', 'messages', (error, messageList) => {
if (error) throw error
console.log('Current messages', messageList);
});
});
The Feathers generator (CLI) :
$ npm install @feathersjs/cli -g