Get Started
Comparing cli-plus to @feathersjs/cli
@feathers-plus/cli, a.k.a. "cli-plus", is similar to @feathersjs/cli in that:
- It uses the same commends, e.g. generate service.
- It prompts with the same questions, e.g. "Which path should the service be registered on?"
- It generates the same modules with essentially identical code.
However cli-plus also:
- Converts DB-agnostic JSON-schema models to the DB model each service is currently configured for.
- Generates all the code needed for your GraphQL Query interface.
- Generates either JavaScript or TypeScript code; with semicolons or without. Converts apps between JS and TS.
- Regenerates you app without losing changes you've made. You stay up-to-date with enhancements and fixes.
Installation
npm i -g @feathers-plus/cli
What the Generator Does
Generators help eliminate boilerplate.
Feathers, even when coded "by hand", eliminates the majority of the boilerplate typically in a CRUD project. Generators will eliminate even more.
Generators.
Feathers generators produce very little code because Feathers is so succinct. You can easily understand the generated code because it's no different from what we've been coding "by hand" so far. Some other frameworks make things “seem” easy by generating thousands of lines of code for you and, in the process, making it almost impossible to implement anything not supported out of the box by their generators.
Generators structure your app.
The generated modules are structured as recommended by the Feathers team.
Generators handle database specifics.
The generators will generate code for different databases so you don't have to investigate how to do so.
Switching between adapters.
You can change a service's database adapter while regenerating it. This allows you, for example, to quickly mock up an app using the NeDB adapter, and then convert it to Mongoose.
Use REST and GraphQL together
REST and GraphQL are totally different. Each is dope if used for the right thing.
cli-plus allows us to specify our services (a REST concept) along with their models and relations. It then generates an additional GraphQL endpoint.
REST vs GraphQL
FeathersJS, using cli-plus, now supports both REST and GraphQL architectural concepts and their query languages.
generate options
Let's generate a project having users who may be members of one or more teams. We want to display teams with all their members.

We first create a folder to contain the app, and then set its generator options.
mkdir feathers-app
cd feathers-app
feathers-plus generate options
Prompts

Source scan took 0s 23ms. The generator scans the app to identify any custom code. This will always be very fast, taking much less time than what theYeomantool needs to start up.The generator will not change the following modules. The generator can refresh many of the modules in your app, updating them to the latest version of the generator. It does this without changing any of your custom code.
The listed modules are ones which the generator will not update. Once they are written, the generator will not refresh them. Any changes you make will remain. If you delete any of them, the generator will replace them.
This list is displayed as a convenience, so you can refer to it anytime by running generate options and cancelling.
You have additionally prevented the following modules from being changed.You can prevent a.k.a. "freeze" modules to prevent the generator from refreshing them. You normally would not do so but it's useful to have the option. The frozen modules are listed here for convenience.Generate TypeScript code?Defaults to no. Otherwise JavaScript code is generated.Use semicolons?Defaults to yes. Otherwise semicolons are not generated at the end of statements.View module changes and control replacement (not recommended)?Defaults to no. This is a Yeoman feature which allows you to inspect the changes to be made to any module, and to decide whether to apply it. Its useful when you are exploring the raw details of what the generator writes.
Folder
The feathers-app folder now contains one module.

- feathers-gen-specs.json contains the information used to generate the app.
feathers-gen-specs.json
{
"options": {
"ver": "1.0.0",
"inspectConflicts": false,
"semicolons": true,
"freeze": [],
"ts": false
},
"app": {},
"services": {}
}
The generator keeps the prompts used to generate the app in feathers-gen-specs.json.
It can regenerate your entire app from that and the custom code it finds in the modules.
Regeneration.
The generator will default to your previous choices if you rerun feathers-plus generate options. It will make any needed changes to all modules if you change any of the options.
Optional
You need not run feathers-plus generate options if your app will be in JavaScript with semicolons.
generate app
Next we generate the scaffolding for the app.
feathers-plus generate app
Prompts

Project name. Defaults to the folder name.Description. Optional description for the project.What folder should the source files live in?Defaultsrc. The name of the folder containing the server code.Which package manager are you using?Defaultnpm. The alternative isyarn.What types of API are you making?The options areREST- HTTP. Default.Realtime via Socket.io- Default.Realtime via Primus
DB data may be replaced by fake data only when NODE_ENV is (comma separated)Some generated tests might mutate the current DB records; you may also write such tests. It is important these tests not run when the Feathers services are using with production, staging or user acceptance testing DBs. Type a comma separated list of NODE_ENV environment variables during which such tests may run and mutate the DB tables.
Folders
The generator creates some JS modules or TS ones reflecting your choices. The modules are properly wired together and structured as recommended by the Feathers team.
config/ contains the configuration files for the app. production.json values override default.json ones when in production mode, i.e. when you run
NODE_ENV=production node path/to/your/server.js.node_modules/. The generator installs the project dependencies here using either npm, or yarn if that's installed. The dependencies are enumerated in
package.json.public/ nontains the resources to be served. A sample favicon and HTML file are included.
src/ contains the Feathers server.
hooks/ contains your custom hooks, usually those general enough to be used with multiple services. A simple but useful logger is provided as an example.
middleware/ contains your Express middleware.
refs contains definitions for fields which may reside in multiple database models.
services/ will contain the services.
index.?s is used by node to start the app.
app.?s configures Feathers and Express.
app.hooks.?s contains hooks which are run for all services.
channels.?s contains the channels sending realtime events to clients.
logger.?s configures the Winston logger.
app.interface.ts contains the global
Appinterface needed to provide typed services.typings.d.ts contains declarations of modules that come without typings.
test/ contains the tests for the app. app.test.?s tests that the index page appears, as well as 404 errors for HTML pages and JSON.
.editorconfig is compatible with the EditorConfig project and helps developers define and maintain consistent coding styles among different editors and IDEs.
.gitignore specifies intentionally untracked files which git, GitHub and other similar projects ignore.
feathers-gen-specs.json contains the information used to generate the app.
LICENSE contains the license so that people know how they are permitted to use it, and any restrictions you're placing on it. It defaults to the Feathers license.
package.json contains information which npm, yarn and other package managers need to install and use your package.
package-lock.json is created by npm. It contains the versions of the module dependencies which are installed. It exists only if the npm option is selected.
README.md contains the documentation for the app.
tsconfig.json contains the TypeScript compiler's configuration used for building the app.
tsconfig.test.json contains the TypeScript compiler's configuration used for building tests.
tslint.json contains tslint configuration.
yarn.lock is created by yarn. It contains the versions of the module dependencies which are installed. It exists only if the yarn option is selected.
TypeScript
src/app.interface.ts contains a type named App made up of all the generated services’ interfaces.
This makes TypeScript aware of the available services’ names, as well as their interfaces.
Because of this app.service('messages') returns a typed service which in the end leaves you with a properly typed response.

TypeScript on the Client
You can have the typings in your client code too by referencing the src/app.interface.ts module.
Adding your code
@feathersjs/cli's job ends when it generates the app scaffolding.
It doesn't know what you do afterwards with it.
Cli-plus is a round-trip generator.
Round-trip generators can take previously generated code, identify custom changes made to it,
and regenerate the code (maybe using different responses to the prompts)
along with those custom changes.
Cli-plus completes the round trip: generate -> customize -> regenerate -> customize -> ....
The developer and cli-plus are in a more collaborative relationship. They can work co-operatively on the scaffolding code.
Retain developer modifications
You will usually add your own code to the generated modules. Cli-plus can identify such additional code, as long as certain standards are followed, and it will retain that added code when regenerating modules.
Some of the code generated by cli-plus is identified as default code which you may want to customize. Any customized code replacing the default code is also retained when modules are regenerated.
Let's look at a trivial example of these features.
@feathersjs/cli and cli-plus both create module src/index.js when generate app is run.
The cli-plus module has some extra decorative comments:
server.on('listening', () => {
// !<DEFAULT> code: listening_log
logger.info('Feathers application started on http://%s:%d', app.get('host'), port);
// !end
});
// !code: end // !end
Starting the server produces the expected log:
Feathers application started on http://localhost:3030
The lines between // !<DEFAULT> code: listening_log and // !end contain default code named listening_log.
The // !code: end // !end line identifies a location named end where additional lines may be added.
Let's change the code to:
server.on('listening', () => {
// !code: listening_log <-- Note that <DEFAULT> was removed.
logger.info('Hello world on http://%s:%d', app.get('host'), port);
// !end
});
// !code: end
logger.info('Initialization complete. Waiting for server to start.');
// !end
Starting the server now logs:
Initialization complete. Waiting for server to start.
Hello world on http://localhost:3030
A common error
Notice the <DEFAULT> tag was removed from listening_log along with changing the default code itself.
Any code changes you make in location_log will be lost on regeneration if you don't remove that <DEFAULT>.
Let's say you originally ran generate app selecting only socket.io as a transport.
You then changed the code in src/index.js as described above.
Later on you realize you also need REST as a transport.
You can just rerun generate app with cli-plus and select both transports.
The regenerated modules will contain the code changes you made above.
Where can code be added?
The short answer is "just about anywhere". Insertion points are available anywhere it makes any sense to add code.
Here is a typical src/services/index.js:
// Configure the Feathers services. (Can be re-generated.)
let comment = require('./comment/comment.service');
let like = require('./like/like.service');
let post = require('./post/post.service');
let relationship = require('./relationship/relationship.service');
let user = require('./user/user.service');
let graphql = require('./graphql/graphql.service');
// !code: imports // !end
// !code: init // !end
let moduleExports = function (app) { // eslint-disable-line no-unused-vars
app.configure(comment);
app.configure(like);
app.configure(post);
app.configure(relationship);
app.configure(user);
app.configure(graphql);
// !code: func_return // !end
};
// !code: exports // !end
module.exports = moduleExports;
// !code: funcs // !end
// !code: end // !end
All the modules follow the same standards, e.g. starting with imports and init,
and ending with funcs and end.
Functions tend to end with names like func_return so you can add code to them.
You can replace or mutate the required modules at init as well as adding any initialization code.
You can modify or replace the exported value at exports.
Its trivial to add insertion points into the generator, so create an issue if you need additional ones. We'll add that insertion point, and you just regenerate the app to be able to use it.
More realistic code customization
The previous example gets the idea across but it's too trivial to appreciate the impact of the feature.
Cli-plus generates a module for every service named src/services/serviceName/serviceName. validate.js.
It contains JSON-schema which may be used to validate record contents before create, update and patch calls.
Part of the code may be similar to:
const base = merge({},
// !<DEFAULT> code: base
{
$schema: "http://json-schema.org/draft-05/schema",
title: "User",
description: "User database.",
required: [
"uuid",
"email",
"firstName",
"lastName"
],
properties: {
_id: {
type: ID
},
uuid: {
type: ID
},
email: {
type: "string"
},
firstName: {
type: "string", maxLength: 30,
},
lastName: {
type: "string", maxLength: 30,
}
}
},
// !end
// !code: base_more // !end
);
This 'base' JSON-schema is derived from the service model. (The service model is a new concept cli-plus introduces which is explained below.) It is the basis for the validation schemas used for the different types of calls.
The developer may replace the default code named base to suit his use case.
It would probably be better however to mutate the default schema by adding, mutation or removing
properties by adding custom code to base_more, e.g.:
// !code: base_more
{
properties: {
firstName: { minLength: 45 },
initial: { type: 'string', maxLength: 1 }
}
}
// !end
This would be better because now, when you change the service model,
cli-plus will make appropriate changes to the base schema in its default code.
This is better than remembering to modify the base schema manually every time you change the service model.
GraphQL examples
One of the main features of cli-plus is its ability to generate a GraphQL endpoint as well as the necessary resolvers. A large number of carefully coded resolvers need to defined in a reasonably sized project, so automatically generating these resolvers is a valuable feature.
However resolvers often have to be customized in unexpected ways.
You may need to change the sort order.
You may need to set props in context.params for certain hooks.
There is no practical end to the potential customizations.
Here are some code snippets in src/services/graphql/service.resolvers.js which cli-plus may generate for a GraphQL endpoint:
You can customize them as you wish, by defining a $sort order for example.
fullname is a calculated field.
Cli-plus, rather than inventing some specialized way for you to indicate what the calculation is,
just creates some default code for you to replace with the calculation.
For example
// fullName: String!
fullName:
// !code: resolver-User-fullName-non
(parent, args, content, ast) => `${parent.firstName} ${parent.lastName}`,
// !end
Avoiding customization
You can have cli-plus generate modules and then prevent it from making changes thereafter to some of them,
by using the options.freeze prop in feathers-gen-specs.json.
Some details
The leaders for custom code may be : // !code:, // !<> code:, //!code:, or // ! code:.
The trailers may be: // !end, or //!end
generate authentication
Let us next generate authentication for the app. This will generate
- The authentication code itself.
- A service for the users table.
- A connection string for the database containing the users table.
feathers-plus generate authentication
Prompts

Authentication questions:
What authentication providers do you want to use?Choose one or more of: Local, Auth0, Google, Facebook and Github.What is the name of the user (entity) service?Default users. The name of the users table.
Service questions:
What would you call one row in the <users> database?Our default is user. This response is used to form the names of GraphQL types e.g.Userand[User!].What kind of service is it?Default is NeDB. Choose one of: Custom service, Memory, NeDB, MongoDB, Mongoose, Sequelize, KnexJS.Generate service in which nested folder, e.g. v1/blog? (optional)Default is none. Nested folders are explained ingenerate service.Which path should the service be registered on?Default/users.Should this be served by GraphQL?Default yes. Should this service be included in GraphQL Queries?
Connection question:
What is the database connection string?How do we attach to the database? The default is the standard connection string for that database.
Folders
The generator adds some modules to the JS folder or TS one.
models/. Some database adapters require models for their tables. Those models are located here.
- users.model.?s is the model for the users table.
services/users contains code for the users service.
- users.service/?s configures the service.
- users.hooks.?s identifies the hooks to be used with the service.
- users.schema.?s contains the database agnostic JSON-schema model for the service. When a service is initially created. the module contains an empty model as no model has yet been defined.
- users.mongo.?s, users.mongoose.?s, users.sequelize.?s contain the JSON-schema model in users.schema.?s converted to a MongoDB, Mongoose or Sequelize model.
- users.validate.?s contains schemas to validate data for create, patch and update service calls.
- users.interface.ts contains a TypeScript interface describing a single
userentity.
authentication.?s contains the authentication code.
mongoose.?s configures the Mongoose adapter. knex.?s, mongodb.?s, rethinkdb.?s, sequelize.?s and sequelize-mssql.?s models would also be created as needed.
test/service/users.test.?s tests the service can be started.
All those modules
Only one of users.mongo.?s, users.mongoose.?s, users.sequelize.?s is being used at any time. The code generated for users.service.?s and users.model.?s call the right modules for the What kind of service is it? selected.
The modules are all generated so you may include all the custom code you need for all of them. This allows you to rerun generate authentication or generate service at any time to change databases.
Feathers Models
Some database systems use a schema to describe how the data in a database table or collection is organized, as well as how the different schemas relate to one another. Unfortunately, schemas are normally not shareable between different databases. The Mongoose database adapter, for example, will not understand a schema written for the Sequelize database adapter.
However if you use Feathers Models, cli-plus can automatically convert your Feathers model into the schema expected by a particular database adapter.
With Feathers service adapters and Feathers Models you can connect to the most popular databases and query them with a unified interface no matter which one you use. This makes it easy to swap databases and use entirely different DBs in the same app without changing your application code.
JSON-schema
Feathers Models are based on JSON-schema. JSON-schema is the most popular way to describe the structure of JSON data and, since JSON data is essentially just plain old JavaScript objects, this makes JSON-schema a great fit for Feathers Models.
JSON-schema:
- has the widest adoption among all standards for JSON validation.
- is very mature (current version is 6).
- covers a big part of validation scenarios.
- uses easy-to-parse JSON documents for schemas.
- is platform independent.
- is easily extensible.
- JSON-schema is easy to write, and there are some great tutorials.
The validateSchema
common hook already uses JSON-data for verification.
Ecosystem
JSON-schema has a large ecosystem. The JSON-schema manual serves as an introduction to it.
Adding the Feathers Model
The users service was created with an empty Feathers model, i.e. it has no fields. Let's now define a model for it in module src/services/users/users.schema.?s. The guide contains detailed information on defining models.
Our resulting model contains 4 fields, and an id field will be added automatically.
// Define the model using JSON-schema
let schema = {
// !<DEFAULT> code: schema_header
title: 'Users',
description: 'Users database.',
// !end
// !code: schema_definitions // !end
// Required fields.
required: [
// !code: schema_required
'email',
'firstName',
'lastName',
'roleId'
// !end
],
// Fields with unique values.
uniqueItemProperties: [
// !code: schema_unique // !end
],
// Fields in the model.
properties: {
// !code: schema_properties
id: { type: 'ID' },
email: {},
firstName: {},
lastName: {},
password: {},
roleId: { type: 'ID' }
// !end
},
// !code: schema_more // !end
};
type: 'ID' indicates the field contain a key (id) or a foreign key (roleId), that is, the field contains the key to another record.
type: {} The default is 'string'.
Centralizing field definitions
Rather than defining the properties of the same type of field, e.g. description, in every model its defined in, you can define the field type once and refer to it from models using the $ref property.
This is an important concept in JSON-schema and its explained elsewhere.
Regenerating the Service
We can now regenerate the service. The generator will detect the Feathers model and use it to "fill in" modules such as users.mongoose.?s and users.validate.?s.
feathers-plus generate service

The responses used to create the users service are the defaults presented now. Just Enter through all the prompts.
The folder organization will not change, however all modules now reflect the Feathers model. src/services/users/users.mongoose.js is an example:
As is src/services/users.users.validate.js:
generate secret
generate authentication added a secret to package.json:
{
"host": "localhost",
"port": 3030,
"public": "../public/",
"paginate": {
"default": 10,
"max": 50
},
"authentication": {
"secret": "1a3808d2bfd79fc3370b0bdcf76ee95b4f9833894041aeb4f0964b99a9a02929c6cdc64180c4a55a02fd6245a6ee462501c40def69d3984ce17f93967ea389918e09fad8ea1efeb7579f4515d66fd965a235604e5c356a6a71c4f999cba05d8ef56eee787364fe086741099bd83cbef21e983782d0d5032ea6d9475518b2521388df49a460b430d76494076772fe57bb9725be1c23dd9f704daba8b4c86340173314860cccec9bc39b25a90390ac5d60823737d6a3d5067a7a44994f22f2f7268b59732853084eccada87d66962fe8d239f16d1fa7c795500e23bd56dc8d64357f9fd33631649a3cba4b5ce50d8b47a7c4117ac84f2aed13157e1447e21bb99c",
// ...
},
"mongodb": "mongodb://localhost:27017/feathers_app"
}
If you need to create a new secret, run
feathers-plus generate secret

and paste the new secret into config/default.json.
generate service
Let's add 2 new services using:
feathers-plus generate service
First let's add a roles service.

And then a teams service.

Nested Folders
The Generate service in which nested folder, e.g. v1/blog? (optional) prompt allows you to group related services together.
Their modules, models, tests and (optionally) service call paths will reflect the organization.
Adding the Feathers Models
Now we add a Feathers model to src/services/roles/roles.schema.?s:
let schema = {
// !<DEFAULT> code: schema_header
title: 'Roles',
description: 'Roles database.',
// !end
// !code: schema_definitions // !end
// Required fields.
required: [
// !code: schema_required
'name'
// !end
],
// Fields with unique values.
uniqueItemProperties: [
// !code: schema_unique // !end
],
// Fields in the model.
properties: {
// !code: schema_properties
id: { type: 'ID' },
name: {}
// !end
},
// !code: schema_more // !end
};
And one to src/services/teams/teams.schema.?s:
// Define the model using JSON-schema
let schema = {
// !<DEFAULT> code: schema_header
title: 'Teams',
description: 'Teams database.',
// !end
// !code: schema_definitions // !end
// Required fields.
required: [
// !code: schema_required
'name',
'members'
// !end
],
// Fields with unique values.
uniqueItemProperties: [
// !code: schema_unique // !end
],
// Fields in the model.
properties: {
// !code: schema_properties
id: { type: 'ID' },
name: {},
members: {
type: 'array',
items: [{ type: 'ID' }]
}
// !end
},
// !code: schema_more // !end
};
Regenerating the Services
And regenerate, accepting the defaults by pressing Enter.
feathers-plus generate service


The Mongoose models now reflects the Feathers model:
/* eslint quotes: 0 */
// Defines Mongoose model for service `roles`. (Can be re-generated.)
const merge = require('lodash.merge');
// eslint-disable-next-line no-unused-vars
const mongoose = require('mongoose');
// !code: imports // !end
// !code: init // !end
let moduleExports = merge({},
// !<DEFAULT> code: model
{
name: {
type: String,
required: true
}
},
// !end
// !code: moduleExports // !end
);
// !code: exports // !end
module.exports = moduleExports;
// !code: funcs // !end
// !code: end // !end
/* eslint quotes: 0 */
// Defines Mongoose model for service `teams`. (Can be re-generated.)
const merge = require('lodash.merge');
// eslint-disable-next-line no-unused-vars
const mongoose = require('mongoose');
// !code: imports // !end
// !code: init // !end
let moduleExports = merge({},
// !<DEFAULT> code: model
{
name: {
type: String,
required: true
},
members: [
String
]
},
// !end
// !code: moduleExports // !end
);
// !code: exports // !end
module.exports = moduleExports;
// !code: funcs // !end
// !code: end // !end
Folders
The generator adds some modules to the JS folder or TS one.
models/teams.model.?s contains the model for teams.
services/teams contains modules for teams.
test/services/teams.test.?s tests the team service is configured.
generate fakes
In Development
"feathers-plus generate fakes" is in beta test. There may be bugs. These docs may not be up to date.
So far we've generated the code for our application. However there isn't very much we can do with that code because the services have no data.
cli+ skirts this issue by providing comprehensive features for generating fake data based on your JSON-schema models. This includes generating values for foreign key fields which refer to other generated records.
userID: '507f1f77bcf86cd799439011'
cli+ is notable in that few other data generators can populate foreign key fields.
Generating appropriate fake data
We'd like the fake data to resemble what our real data would look like.
- email address fields that look like valid addresses.
- phone number fields that look like phone numbers tailored for a particular locale.
- avatar references that point to real images.
We can describe the fake data we want
by adding a faker, chance and format prop to each field's JSON-data.
- fakeRecords: 6 - Generate 6 faked records for this service. The default, which you can change, is 5 records.
- id or _id - The record's key field does not need a faking prop.
Key values appropriate for the service adapter
A key value will be generated that's suitable for that service's adapter. So MongoDB, Mongoose services use ObjectId strings, while Sequelize, Knex ones use increasing integers.
- chance: { hash: { length: 60 } } - generates a 60 character hash value.
- faker: { fk: 'roles:random' } - uses the key from a randomly generated record in the roles service.
A wealth of options
There are about 150 different types of faker props avaliable. They can be localized to about 35 locales so, for example, your fake data can be in Spanish or German.
There are also over 100 chance props available.
Custom props
You can also add your own custom faker, chance and format props.
Prompts
Now that we've described what the fake data should look like, we can generate it.
feathers-plus generate fakes

Folders
The generator adds two modules to the app.
seeds/fake-data.json. The generated fake data.config/defaults.js. Contains the configuration used to generate the fake data. You can change any of its options and rerunfeathers-plus generate fakes.
Further customizing the generated data
The generated fake data will also satisfy the other JSON-schema specified for the field. So the more detailed your JSON-schema, the more the generated data will resemble what you expect in production.
This more detailed JSON-schema would produce more satisfactory generated data.
email: {
minLength: 10,
maxLength: 30,
faker: 'internet.email',
},
firstName: {
minLength: 2,
maxLength: 20,
faker: 'name.firstName',
},
lastName: {
minLength: 2,
maxLength: 40,
faker: 'name.lastName',
},
It's a win, win.
Not only will a more detailed JSON-schema produce better generated data, it will also produce better validation checking in the automatically generated validation service hooks.
Foreign keys
cli+ also supports foreign keys. We previously saw
roleId: {
type: 'ID',
faker: { fk: 'roles:random' },
},
faker: { fk: 'roles:random' } is a custom prop defined by cli+. It populates the field with the key from a randomly selected record from the roles service.
Circular foreign keys
There are no restrictions on the foreign keys. Services A and B may each have a foreign key referencing the other. Service A may reference service B, which references service C, which then references back to service A.
The following syntax is supported:
- 'roles:random:fieldName' A random record is selected from the roles service. The value of its fieldName field is used.
- 'roles:random' - The value of the id or _id field is used.
- 'roles' - The same as 'roles:random'.
TIP
fieldName may be in dot notation, e.g. "address.city".
Arrays of distinct foreign keys
The above syntax alone cannot handle several needs we may have. First, we may need an array of distinct foreign keys:
The memberIds field may contain duplicate foreign keys if we use faker: { fk: 'users' } or faker: { fk: 'users:random' }.
The list of keys for the roles service is shuffled at the start of each teams record. The faker: { fk: 'users:next' } will use the next one of these shuffled keys. Keys will start to be reused only once all existing keys have been used.
This ensures the foreign keys in memberIds are distinct.
Extracting multiple fields from a related record
We may need to copy the values of several fields from the same related record. For example we may need a result like
memberIds: [{ id, firstName }, { id, firstName }, ... ]
We can request this as follows
'users:next' selects the record containing the next shuffled key. Then 'users:curr:firstName' refers to this current shuffled record, that is, the same one which provided the key.
You can extract any number of fields from a related record using this technique.
Expressions
- Sometimes an expression has to be calculated in order to populate a field.
properties: {
// !code: schema_properties
id: { type: 'ID' },
// ...
dayOfWeek: {
type: 'integer',
faker: { exp: 'new Date("December 25, 2018").getDay()'}
}
// !end
},
faker: { exp: 'new Date("December 25, 2018").getDay()'} is a custom prop defined by cli+. It populates the field with the calculated day of the week.
- You can reference other fields in the record within the expression.
properties: {
// !code: schema_properties
id: { type: 'ID' },
// ...
date: {
faker: 'date.soon', // e.g. "2018-12-25T04:49:43.661Z"
},
dayOfWeek: {
type: 'integer',
faker: { exp: 'new Date(rec.date).getDay()' }
}
// !end
},
rec is an object passed to the expression containing the record being populated. You can use it in any valid JavaScript expression.
- Other conveniences are also passed to the expression in the
ctxobject.- ctx.data (Array< Object >) - All the generated records for all the services. Values for foreign keys and expressions have been calculated up to the current record.
- ctx.hashPassword (Function(password)) - Calculates a password hash
compatible with Feathers'
hashPasswordhook. This password will work correctly with Feathers local authentication. - fakeData.expContext from module config/default.js is also merged into ctx. You can refer to any of those merged properties in the expression.
- ctx.tablesInfo {Object) - A structure you can use to determine the current shuffled key for each service. Its structure is:
Passwords
You can generate password hashes two ways
faker: { exp: 'ctx.hashPassword("my secret password")' }generates a Feathers compatible password you can use with local authentication. Its slow, taking over 1 second per password.chance: { hash: { length: 60 } }quickly generates a random hash the same length as Feathers'. Its not compatible with Feathers' local authentication.
Customizing data generation
You can define your own custom props for faker, chance and format. They are configured in config/default.js and that module contains documentation on how to define and use them.
You can also modify the generated data before it is written to seeds/fakeData.json with the postGeneration property in config/default.js.
GraphQL
There is a good deal of literature available on what GraphQL is, on why it is good, and how it compares to REST.
- Why GraphQL
- Is GraphQL The Future?
- Why GraphQL is the future And what it means for you.
- GraphQL vs. REST Two ways to send data over HTTP: What’s the difference?
- GraphQL vs REST: Overview
Some take-aways are:
- REST and GraphQL are totally different.
- GraphQL isn't a magic bullet, nor is it better.
- You can definitely use both at the same time.
- GraphQL is dope if used for the right thing.
cli-plus automatically generates a GraphQL endpoint for your Feathers services, so you may make queries using either REST or GraphQL, even simultaneously.
Our app as an example
We've taken the app we've been working on and
produced a GraphQL example .
You can run it yourself with npm i and npm start.
- We converted the services to NeDB so the example is easy to run.
- We added some additional information to the service schemas.
- We initialize the NeDB tables in src/index.?s.
- We also run some GraphQL queries there.
Types
STOP
Don't run this command right now. Its shown here for illustrative purposes.
When you run
feathers-plus generate graphql
GraphQL types are generated, along with the fields on those types. The types generated for our example are:
type Role {
id: ID
name: String!
}
type Team {
id: ID
name: String!
memberIds: [String]
members: [User!]
}
type User {
id: ID
email: String!
firstName: String!
lastName: String!
password: String
roleId: ID!
fullName: String!
role(query: JSON, params: JSON, key: JSON): Role
teams(query: JSON, params: JSON, key: JSON): [Team!]
}
type Query {
getRole(key: JSON, query: JSON, params: JSON): Role
findRole(query: JSON, params: JSON): [Role]!
getTeam(key: JSON, query: JSON, params: JSON): Team
findTeam(query: JSON, params: JSON): [Team]!
getUser(key: JSON, query: JSON, params: JSON): User
findUser(query: JSON, params: JSON): [User]!
}
The getRole, findRole, etc. are the GraphQL queries you can make. The other type expand what the queries can return.
Notice the API for the types is the Feathers service API by default.
- key is the Feathers id,
- query is Feathers params.query,
- params is anything else in Feathers params.
Using the same API reduces what you have to learn, and makes the interface between Feathers and GraphQL seamless.
JSON
You may be perplexed by the JSON type used above as it's not one of GraphQL's scalar types. It is a custom type added by @feathers-plus/graphql.
In GraphQL notation
- String is a string or null.
- String! is a string.
- [String] is an array of strings, some of which may be null. It may also be null instead of an array.
- [String]! is similar to [String] but it cannot be null.
- [User!] is an array of User objects. None of the elements may be null.
Making our first request
The client (or the server) can then make a request like
const graphql = app.service('graphql');
const myQuery = '{
findUser(query: {}) {
firstName
lastName
fullName
email
role { name }
teams { name }
}
}';
const result = await graphql.find({ query: { query: myQuery } })
GraphiQL
Why the two query in { query: { query: myQuery } }?
The first is needed by the Feathers service API of course.
The second one is necessary in order to optionally use GraphiQL,
the popular graphical interactive in-browser GraphQL IDE.
You can use GraphiQL to test your queries from the browser.
Darn that `$`
$ is a reserved char in GraphQL, so the following call will not behave as expected
graphql.find({ query: { query: 'findUser(query: { $sort: { lastName: 1 } })' } })
@feathers-plus/graphql's workaround is to code double underscores __ instead of a $.
Instead of the above, we would use
graphql.find({ query: { query: 'findUser(query: { __sort: { lastName: 1 } })' } })
In short, replace every $ you would use in Feathers with __ in the GraphQL request.
The request returns data of the form:
{
total: 3,
limit: 10,
skip: 0,
data: {
findUser: [
{
firstName: 'Jack',
lastName: 'Doe',
fullName: 'Jack Doe',
email: 'jack-doe@gmail.com',
role: { name: 'normal' },
teams: [ { name: 'beta' } ]
}, {
firstName: 'Jane',
lastName: 'Doe',
fullName: 'Jane Doe',
email: 'jane-doe@gmail.com',
role: { name: 'normal' },
teams: [ { name: 'alpha' }, { name: 'beta' } ]
}, {
firstName: 'John',
lastName: 'Doe',
fullName: 'John Doe',
email: 'john-doe@gmail.com',
role: { name: 'admin' },
teams: [ { name: 'alpha' } ]
}
]
}
}
Some of the advantages of GraphQL are becoming clear:
- The complete response was returned in a single request.
- The client asks only for the data it needs.
- The client does not know, nor does it care how the data is organized, or what needs to be done to accumulate the complete response.
- No versioning exists.
Making another request
And a request like:
{
findTeam(query: {}) {
name
memberIds
members {
fullName
role { name }
}
}
}
returns
{
total: 2,
limit: 10,
skip: 0,
data: {
findTeam: [
{
name: 'alpha',
memberIds: [ 'gXWQs77x3X1fZjVg', 'hVJjFMnxCfHnIBS0' ],
members: [
{ fullName: 'Jane Doe', role: { name: 'normal' } },
{ fullName: 'John Doe', role: { name: 'admin' } }
]
}, {
name: 'beta',
memberIds: [ '6bJA2ONlIEpA8IX8', 'hVJjFMnxCfHnIBS0' ],
members: [
{ fullName: 'Jack Doe', role: { name: 'normal' } },
{ fullName: 'Jane Doe', role: { name: 'normal' } }
]
}
]
}
}
Resolvers
GraphQL expects to find resolvers to stitch the response together. Each resolver is a function which joins one type to another, or which calculates a derived field.
feathers-plus generate graphql generates the resolver code needed for your GraphQL endpoint,
and it generates up to 3 versions of the resolvers
- One uses normal Feathers service calls.
- Another uses batch-loaders.
- And the final one uses raw SQL statements.
The resolvers usually require a lot of code which must be meticulously correct. You will save a lot of time by having the generator write the resolvers.
For example, this is the resolver code generated for our simplist example for the straightforward case --- using normal Feathers service calls.
Comprehensive example
@feathers-plus/cli-generator-example contains a comprehensive GraphQL example produced with cli-plus. It's based on the following data schema

It has a test-harness running on the browser
You can select queries, modify them, run them and see the results.
The repo contains examples in both JavaScript and TypeScript, implementing the same test harness for:
- Feathers service resolvers used with the NeDB database.
- BatchLoader based resolvers used with the NeDB database.
- Feathers service resolvers used with Sequelize backed by the SQLite database.
- BatchLoader based resolvers used with Sequelize backed by the SQLite database.
- Resolvers producing raw SQL statements used with Sequelize backed by the SQLite database.
You will note how many lines of code are required to implement GraphQL resolvers, and thankfully cli-plus generates them.
GraphQL extensions to models
Now that we understand what we expect to implement with the GraphQL endpoint, let's see how to accomplish it.
src/services/users/users.schema.?s contains an outline of GraphQL extensions to the users Feathers model.
// Define optional, non-JSON-schema extensions.
let extensions = {
// GraphQL generation.
graphql: {
// !<DEFAULT> code: graphql_header
// name: 'User',
// service: {
// sort: { _id: 1 },
// },
// sql: {
// sqlTable: 'Users',
// uniqueKey: '_id',
// sqlColumn: {
// __authorId__: '__author_id__',
// },
// },
// !end
discard: [
// !code: graphql_discard // !end
],
add: {
// !<DEFAULT> code: graphql_add
// __author__: { type: '__User__!', args: false, relation: { ourTable: '__authorId__', otherTable: '_id' } },
// !end
},
// !code: graphql_more // !end
},
};
Let's start filling it in.
Metadata
- name The name of the GraphQL type for this service. It defaults to your answer in generate service for What would you call one row in the users database?
name: 'User',
Being included in the GraphQL endpoint
The service will not be included in the GraphQL endpoint until it's given a name.
Queries
Two queries are generated for each service. getUser is similar to Feathers users.get(id) and returns a single record. findUser is similar to users.find({ query: {...} }) and returns multiple records.
You can implement other queries by adding custom code to the resolver modules.
service This property must be defined for Feathers service-based resolvers to be generated.
- service.sort How to sort the users records if users form the top level of the response.
service: {
sort: { lastName: 1, firstName: 1 },
},
STOP
The service property must be defined for Feathers service-based resolvers to be generated for this service.
sql This property must be defined for resolvers producing raw SQL statements to be generated.
- sql.sqlTable The name of the SQL table. Defaults to the service name.
- sql.uniqueKey The name of the SQL column containing the primary key.
- sql.sqlColumn Allows you to relate SQL column names to JavaScript variables if they differ, e.g. the SQL column name email_address to the JavaScript email.
sql : {
email: 'email_address'
}
STOP
The sql property must be defined for resolvers producing raw SQL statements to be generated for this service.
join-monster
The generator uses join-monster to do the heavy lifting. You have to read and understand its documentation.
- discard Fields not to be included in the GraphQL endpoint. If our users service had included a password field, it would be reasonable to have
discard: [
// !code: graphql_discard
'password'
// !end
],
- add defines how other records are joined to this service, and how any computed fields are calculated.
Our Feathers model alone would produce the GraphQL type
type User {
id: ID
email: String!
firstName: String!
lastName: String!
password: String
roleId: ID!
}
Let's continue to expand the model to include joined records and a computed field.
Computed fields
You need only mention that a computed field exists.
add: {
// !code: graphql_add
fullName: { type: 'String!', args: false },
// !end
},
- fullName The name of the computed field.
- type The GraphQL type of the result.
- args: false The field is not permitted arguments in the GraphQL Query. The next section has more information on args.
GraphQL scalar types
GraphQL comes with a set of default scalar types out of the box:
- Int A signed 32‐bit integer.
- Float A signed double-precision floating-point value.
- String A UTF‐8 character sequence.
- Boolean true or false.
- ID The ID scalar type represents a unique identifier, often used to refetch an object or as the key for a cache. The ID type is serialized in the same way as a String; however, defining it as an ID signifies that it is not intended to be human‐readable.
The GraphQL type system identifies the shape of a field
- String The field is a String. It may also be null.
- String! Its a String. null is not allowed.
- [String] Its an array of Strings. An element may be null and, instead of an array, the field itself may be null.
- [String!] Like [String] but the elements may not be null. null is still allowed instead of an array.
- {String]! Like [String] but an array must exist.
- [String!]! Like [String] but an array must exist and no element may be null.
Note we do not say how the field is calculated. cli-plus will generate resolver functions in src/services/service.resolvers.?s and /batchloader.resolvers.?s like
User: {
// fullName: String!
fullName:
// !<DEFAULT>code: resolver-User-fullName-non
(parent, args, content, ast) => { throw Error('GraphQL fieldName User.fullName is not calculated.'); },
// !end
}
And we customize it with the calculation.
User: {
// fullName: String!
fullName:
// !code: resolver-User-fullName-non
(parent, args, content, ast) => `${parent.firstName} ${parent.lastName}`,
// !end
}
We would customize the SQL resolvers in src/services/graphql/sql.metadata.?s
User: {
sqlTable: 'Users',
uniqueKey: 'uuid',
fields: {
// fullName: String!
fullName: {
// !code: fields-User-fullName-non
sqlExpr: (tableName, args) => `${tableName}.firstName || ' ' || ${tableName}.lastName`
// !end
},
},
}
After defining the calculated field, our GraphQL type is
type User {
id: ID
email: String!
firstName: String!
lastName: String!
password: String
roleId: ID!
fullName: String!
}
Joining records
Our schema is

To each users record we can attach
- The single roles record referred to in its users.roleId.
- All the teams records containing its users.id in their teams.memberIds[].
To each roles record we can attach
- All the users record containing its roles.id in users.roleId.
To each teams record we can attach
- All the users records whose users.id is contained in its teams.memberIds.
Joining Roles record to Users
Our users model contains roleId, a foreign key to the roles model. We can join the proper roles record to the user record with
add: {
// !code: graphql_add
role: { type: 'Role', args: true, relation: { ourTable: 'roleId', otherTable: '_id' } },
// !end
},
- role The property added to users containing the roles record.
- type Identifies the type of object the resolver function will return. Here it returns a roles object or null.
The GraphQL type system identifies the shape of a field
- Role The field is a Role object. It may also be null.
- Role! Its a Role object. null is not allowed.
- [Role] Its an array of Role objects. An element may be null and, instead of an array, the field itself may be null.
- [Role!] Like [Role] but the elements may not be null. null is still allowed instead of an array.
- {Role]! Like [Role] but an array must exist.
- [Role!]! Like [Role] but an array must exist and no element may be null.
- args Determines if arguments are allowed for the field allowed in the GraphQL Query. cli-plus always generates Feathers compatible arguments:
role(query: JSON, params: JSON, key: JSON): Role
Where
- key Equivalent to Feathers id, i.e. users.get(id).
- query Equivalent to Feathers query, i.e. users.find({ query: {...} }).
- params Equivalent to Feathers params, i.e. users.find(params). The query and params arguments are merged together as expected by Feathers.
relation The corresponding fields in each model which define the relationship.
- ourTable The field name in the model whose metadata we are defining.
- otherTable The field name in the other service.
Resolver functions
If ourTable and otherTable are both scalars, the resolver function would be similar to
roles.find({ query: { _id: parent.roleId }, paginate: false })
If ourTable had been an array, then the resolver function would be like
roles.find({ query: { _id: { $in: parent.roleIds } }, paginate: false })
If otherTable is an array, the resolver function will be similar to
if (!(content.cache.User && content.cache.User.teams)) {
content.cache.User = content.cache.User || {};
content.cache.User.teams =
teams.find({ query: {...}, paginate: false }).then(extractAllItems);
}
return Promise.resolve(content.cache.User.teams)
.then(res => res.filter(rec => rec.memberIds.indexOf(parent._id) !== -1));
STOP
ourTable and otherTable cannot both be arrays.
After defining roles, our GraphQL type would be
type User {
id: ID
email: String!
firstName: String!
lastName: String!
password: String
roleId: ID!
fullName: String!
role(query: JSON, params: JSON, key: JSON): Role
}
Joining Teams records to Users
We want to join all the teams records that a user belongs to.
add: {
teams: { type: '[Team!]', args: true,
relation: { ourTable: '_id', otherTable: 'memberIds' }, sort: { name: 1 }
},
},
We are joining multiple records rather than a single one.
- sort optionally, how to sort the records being joined.
After defining teams, our GraphQL type would be complete.
type User {
id: ID
email: String!
firstName: String!
lastName: String!
password: String
roleId: ID!
fullName: String!
role(query: JSON, params: JSON, key: JSON): Role
teams(query: JSON, params: JSON, key: JSON): [Team!]
}
The final result GraphQL extension for users is:
// Define optional, non-JSON-schema extensions.
let extensions = {
// GraphQL generation.
graphql: {
// !code: graphql_header
name: 'User',
service: {
sort: { lastName: 1, firstName: 1 },
},
// !end
discard: [
// !code: graphql_discard // !end
],
add: {
// !code: graphql_add
fullName: { type: 'String!', args: false },
role: { type: 'Role', args: true,
relation: { ourTable: 'roleId', otherTable: '_id' }
},
teams: { type: '[Team!]', args: true,
relation: { ourTable: '_id', otherTable: 'memberIds' }, sort: { name: 1 }
},
// !end
},
// !code: graphql_more // !end
},
};
Joins for the roles and teams service.
The GraphQL extension for the roles service is similarly:
// Define optional, non-JSON-schema extensions.
let extensions = {
// GraphQL generation.
graphql: {
// !code: graphql_header
name: 'Role',
service: {
sort: { name: 1 },
},
// !end
discard: [
// !code: graphql_discard // !end
],
add: {
// !code: graphql_add
users: { type: '[User!]', args: false,
relation: { ourTable: '_id', otherTable: 'roleId' }
},
// !end
},
// !code: graphql_more // !end
},
};
And for teams is
// Define optional, non-JSON-schema extensions.
let extensions = {
// GraphQL generation.
graphql: {
// !code: graphql_header
name: 'Team',
service: {
sort: { name: 1 },
},
// !end
discard: [
// !code: graphql_discard // !end
],
add: {
// !code: graphql_add
members: { type: '[User!]', args: false,
relation: { ourTable: 'memberIds', otherTable: '_id' },
sort: { lastName: 1, firstName: 1 }
},
// !end
},
// !code: graphql_more // !end
},
};
generate graphql
We have the GraphQL extensions coded and, during generate service, we indicated which services are to be included in the GraphQL endpoint. We can now generate that endpoint.
feathers-plus generate graphql

- How should Queries be completed? Different versions of GraphQL resolver functions may be generated.
- Using standalone Feathers service calls is the default and the simplest approach. It uses plain Feathers service calls.
- Using BatchLoaders uses caching to eliminate multiple reads of the same information. It also batches individual services calls together to reduce the number of database requests made. BatchLoaders typically make an order of magnitude fewer requests, i.e. 3 instead of 30, than the previous option.
- Using dynamic SQL statements is available only for SQL databases. These resolvers generate a raw SQL statement, so the query is fullfilled by one request to the database.
SQL statments
The raw SQL statement may be very large for a complex query, and that may result in performance problems for such queries.
You should normally use this option when you have good SQL experience. You will also have to become familiar with join-monster which is used by the generated code.
Practicality
We recommend you get your app working using the standalone services option because it's the easiest to debug. You can switch to BatchLoaders if your server is under load or if you need better performance.
It is not clear how more performant raw SQL statements are over BatchLoaders. You can evaluate them if you want better performance than that provided by BatchLoaders.
Which path should the service be registered on? Default is
/graphql.Does the service require authentication? Default is no. As the query is being resolved, authentication is performed for any service being accessed, so these services are not exposed if the GraphQL endpoint itself is not secured.
Folders
The generator adds some modules to the JS folder or TS one.
- graphql.schemas.?s The GraphQL type definitions.
- graphql.service.?s Configures the GraphQL service for the strategy chosen in the How should Queries be completed? question.
- graphql.hooks.?s Feathers hooks for the GraphQL service.
- graphql.interfaces.ts - ?????????????????????????????????????????????
- service.resolvers.?s Resolver functions for the Using standalone Feathers service calls strategy.
- batchloader.resolvers.?s Resolver functions for the Using BatchLoaders strategy.
Several modules are used for the Using dynamic SQL statements strategy:
- sql.resolvers.?s Resolver functions for the strategy.
- sql.metadata.?s Meta data used by join-monster to dynamically construct the raw SQL statement.
- sql.execute.sequelize.?s Execute a raw SQL statement using Sequelize.
- sql.execute.knex.?s Execute a raw SQL statement with Knex.
- sql.execute.custom.?s Execute a raw SQL statement in a custom environment.
All those modules
Only one of service.resolvers.?s, batchloader.resolvers.?s or sql.**.?s is/are being used at any time. The code generated in graphql.service.?s calls the right modules for the How should Queries be completed? selected.
The modules are all generated so you may include all the custom code you need for all of them. This allows you to rerun generate graphql at any time to change strategies.
What We Have Accomplished
REST and GraphQL are totally different. Each is dope if used for the right thing.
cli-plus allows us to specify our services (a REST concept) along with their models and relations. It then creates our GraphQL endpoint, and you'll infrequently have to customize that.
In Short
FeathersJS, using cli-plus, now supports both REST and GraphQL architectural concepts and their query languages.
generate all
cli-plus will change over time: bug fixes, enhancements, retooling for new version of FeathersJS. You can upgrade your app to the latest version of cli-plus by running
feathers-plus generate all

This will regenerate most of the modules, keeping you custom code, while generating the latest cli-plus boilerplate.
Be Safe
You may want to backup you project before running generate all until you feel comfortable no longer doing so.
Modules Regeneration
cli-plus will not override certain modules if they already exist. We have already seen the names of these when we looked at generate options

Freezing modules
You can prevent cli-plus from changing other modules it would otherwise regenerate. feathers-gen-specs.json contains all the information needed to recreate your app (in addition to your custom code of course). It starts with:
{
"options": {
"ver": "1.0.0",
"inspectConflicts": false,
"semicolons": true,
"freeze": [],
"ts": false
},
cli-plus will not overwrite any module names you add to freeze. For example
"freeze": ["src/hooks/logger.js"]
Custom code
You can list all the custom code in your app using
feathers-plus codelist
Next Steps
You should try generating a simple app now. Get it running first without a GraphQL endpoint, then add the endpoint in.
You can find more specialized information in
- Guide - More details on particular subjects.
- API - The API for various functions called by the generated code. You may want to use them yourself in your your custom code.
- JSON-schema - An introduction to the extensive JSON-schema ecosystem.
- Maintainers - Useful information if you want to contribute to this project. Design ideas for future enhancements.
Good luck!











