We have previously seen a simple todo app using FeathersJS(and NeDB).
Feathers makes it really easy to develop an API or a real-time app by providing a super-powered baseline that can be extended quickly to create a useful app. However, the example provided earlier does not provide a set of features anywhere near the real-world experience.
So, here we are talking about all the additional things that Feather could do - only if you let it. This is a FeatherJS + ObjectionJS + Feather-Vuex tutorial through building a totally real-world, simple CRM application called ‘FeatherLight CRM’. We name it thus since all awesome apps deserve their own name and identity.
Our Chosen Ecosystem
First, let us get our basic ecosystem right.
Feathers groups together various components and provides more than one component options to do the same job. So, it is that with flutter in my heart, I choose -
- Postgres - possibly the greatest database of our times
- Objection ORM - Benefits of an ORM, and knex for DB migration + operations + low-level query builder
- Local authentication
Next.. since we love our SPAs and scoff at anything more simple, we choose -
- Vue + Vuex
- Feathers-Vuex (this is the main reason for choosing Vue - makes it a cool developer experience.
Feathers
andcool
- did you get it?)
Dealing with all this complexity can spur anxiety, so let us divide the tasks into those required for server and client.
This post primarily deals with server-related tasks, i.e, all things FeathersJS. We will see more of Vue in a subsequent post.
Server: Get Started
Getting started on Feathers is a breeze and is similar to our earlier project.
Install CLI if you don’t have feathers on your computer.
npm i -global @feathersjs/cli
Create the project folder and a client and server folders to store your code.
mkdir featherlight-crm
cd featherlight-crm
mkdir client
mkdir server
cd server
feathers g app
Choose the configuration values for FeathersJS server that talked about five seconds back.
Also, it will be great if you can make the database available at this time - the generator also tries to check for the database.
Open the project folder in your favourite editor - which should be VSCode by now.
Once the app generation is complete, open ./config/default.json
and change the database section to tell knex where migrations need to be stored - both the directories for seed/migrations, and the table in database. This will come in handy in a bit.
|
|
Run your server -
npm run dev
This will not only start the server, but also migrate users
(created by our generator) to the database. Note that we did not create migrations for users - ideally we should have done that, but we are crazy enough to want a practical demo of what is possible with Feathers out of the box vs. how our changes impact the way we work.
Login to your DB with your favourite client (which should be HeidiSQL) to see this table in all its glory.
You can check whether server is running by hitting a few sample end-points in your favourite API client - which should be Insomnia.
API | Result |
---|---|
GET: http://localhost:3030 | 200 OK: Sample feathers HTML |
GET: http://localhost:3030/users | 401 Unauthorized: Not authenticated yet |
POST: http://localhost:3030/users ; input JSON below | 200 OK: User created, response JSON with user id |
POST: http://localhost:3030/authentication ; input JSON below | 200 OK: User logged in, response JSON with JWT |
Input messages -
POST: New User
|
|
POST: Login
|
|
That is it - you have your baseline beautiful project ready.
Server: Folder Structure
Before we proceed, we will have “more words” on the setup.
FeathersJS is straightforward and does not believe in magic. The folder structure reflects that. Some of the key folders along with their functions are outlined below.
Folder Name | What it contains? | Function |
---|---|---|
config | Configuration files for prod & non-prod environments | Provides config values and parameters including server address/port, DB connection strings, auth mechanism, etc. |
public | Any files available to the external world | Typical server implementations use some kind of reverse proxy to expose public files to the outside world |
src | All source code | Key files: app , authentication , knex , objection |
src/models | All models | DB models ready to be migrated |
src/services | Services are at the heart of Feathers | Individual folders for users and other services. Consists of services and hooks. Hooks also specify routes amongst other things |
test | Tests for your app | Automated testing! |
We have to pay attention to services in some detail.
Services will have folders created for each new service (well, there are exceptions and weird logic - but this is typical ). Services have a couple of things going for them -
- Feathers maps REST services to the services defined here - you will be able to make out the similarities between the methods supplied by default and the REST service types
- Services can also be called on websockets and the same service runs happily
- Hooks provide a before/after way of plugging in custom logic for the services
We will see much more of this with a practical example below.
Server: Create new functionality
Let us create a string of entities and functions around the entity to enable our super CRM app.
Create contact service
It may be a good idea to stop your feathers service at this time. Now, use feathers generate command to create a new service.
feathers g service
Answer the questions to create a new service called contacts
. You can select the same Objection
as data source as that chosen while creating the app.
This does some useful things -
- create 3 files under
src/services/contacts/
- service, hooks and class - create a sample test for
contacts
- create
contacts.model
in./src/models/
- update
services/index
with the newcontacts
service
If you had the feathers server running, contacts
table will get created with the default columns specified in contacts.model
. We stopped the service since we don’t want that.
As you can see -
- the new structure for
contacts
is straight-forward and similar to ourusers
example above. - By just running the CLI, you now have the entire
contacts
service + model setup.
The Art of Models and Migration
Open src/models/contacts.model.js
. Add your own fields, and remove logic to migrate tables.
|
|
While the default contact.model.js
is good for initial migration, it becomes tricky to manage future changes. So, we will take the migration statements away from this file, group it elsewhere, and at the same time - streamline migrations.
First, create a file knexfile.js
in project root -
|
|
Next, install knex command line utility globally. This is useful to manage migrations in this project, and in hundreds of other projects that we will created in the next year.
npm i -g knex
You would have already noticed the config
folder in the project root. Make modifications to default.json
..
|
|
The above configuration will set the stage and indicate that a place to store migrations in a DB table called knex_migrations
(not created yet).
We are all set. Run knex to create migration files -
|
|
This will create a template migration file in ./database/migrations/
folder - something like 20200422152442_init.js
.
Cut/paste create table
statements from the contacts.models.js
to this migration file - with a few changes.
|
|
Now, you can take a deep breath and migrate the table to database.
knex migrate:latest
Check in your database to find contacts
table.
But then, while still holding on to the breath, you realise a mistake. There has been one of the hardest typo mistakes that can ever be - status_cd
is wrongly input as status_c
.
No problem, let us change that.
knex migrate:rollback
.. and ta da - the contacts
table is gone from the DB.
Re-run migration -
knex migrate:latest
Did you start breathing again? Well, good for you - that was just in time to realise another miss. You never added address
- tis’ a good thing we are still learning here and not doing a multi-million dollar project. We can always do this later.
Congratulations on completing the database tasks.
Back to the Server
Start your server again ..
npm run dev
You can start doing your REST calls without writing any code to handle contacts
transactions -
1. POST http://localhost:3030/contacts
Input -
|
|
Don’t forget to provide token from the authentication/login call done earlier as a bearer
token.
Output -
|
|
A new contact called abc 1
is created.
2. GET http://localhost:3030/contacts
Input -
< no input >
Output -
|
|
This is the right time to let out a joyous cry - huzzah!
More migrations
You have already seen that feathers generate
has already created the contacts
model. But, what if you want to change the model?
Changing model will imply changes to -
- model
- underlying table
Since feathers
is just the coordinator here, we will just use the full power of underlying ObjectionJS/knex and migrations as seen previously.
In our case, we demonstrated our careful planning abilities by adding address
in our model (yes, this is a mistake. And yes, this is one of the ways of covering up mistakes). So, the model doesn’t need to change.
But, we do have to add address to database. We already have two tables created and are in no mood to rollback a second time. Instead, we add incremental changes to the same table.
Let’s create another migration file -
knex migrate:make contact-address
Change the migration file -
|
|
Run migration -
knex migrate:latest
You will now start seeing an address
column against contacts
table.
Also, you may have already noticed a table called knex_migrations
in your database. That table should have two migration records corresponding to the initial migration and the subsequent incremental migration after doing the above tasks for address.
Knex migrations started shining really bright, didn’t they?
More Entities and Functions
Now that we have walked through an entity and service, and also know how to change them - we can create dozens more with just clicks and copy/paste magic.
Change Users
Previously when you saw how easily we could manage database changes - you were left thinking on how users
was not included into this fold. Feathers migrated that table for us, and we don’t quite have any way to control that migration.
The good news - you can start anytime without a care for the past deeds. You just have to create another migration and repeat the above tasks to create additional columns and manually change the users.models.js
file. It does not matter whether users
was created initially by feathers or created by hand in the backend.
So, let’s do that!
Create migration file -
knex migrate:make user-enhancements
Change the migration file -
|
|
Run migration -
knex migrate:latest
Add Activities Model/Service
It is time to upgrade our app - a CRM app cannot be as simple as having users
and contacts
. Everyone knows that it is the activities
that complete CRM.
By now, you will probably be scoffing and guffawing at adding a new service.
feathers g service
Name this new service activities
, and select all the right options.
Change activities.model.js
-
|
|
Create a migration template for activities -
|
|
Change 20200422192555_activities.js
(the comparative migration file created for you in database/migrations
folder) -
|
|
You perform activities and tasks for a contact. So, one contact can have many activities.
Similarly an activity is done by a user. Hence, we can smartly conclude that activities are related to both users and contacts.
We have played things well and already created contact_id
, user_id
columns in our activities
table. We can also establish the relationship at the model level.
Insert the below code block before $beforeInsert()
in activities.model.js
.
|
|
You can see a pattern emerging here, don’t you? Objection and Knex make it quite easy to establish and manage relationships. (In this case, however, note that we did not tell the DB that contact_id
or user_id
are foreign key fields - that can be easily fixed in the migration files).
You can start performing activity
requests.
-
POST http://localhost:3030/activities
< input and output are the same :) >
1 2 3 4 5 6
{ "type_cd": "Meeting", "status_cd": "Cancelled", "contact_id": 1, "user_id": 1 }
-
GET http://localhost:3030/activities
< no input data >
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
{ "total": 1, "limit": 10, "skip": 0, "data": [ { "type_cd": "Meeting", "status_cd": "Cancelled", "start_date": null, "end_date": null, "due_date": null, "description": null, "created_at": "2020-03-23T04:39:09.182Z", "updated_at": "2020-03-23T04:39:09.182Z", "id": 1 } ] }
Since you already established relationship and storing contact
you may also want contact to be fetched along with activity
details. We established the relationship by changing activity.model.js
but did not tell feathers to get contact details.
That’s quite easy to do and requires two steps.
Step 1: Change activities.service.js
We tell feathers to allow “eager” loading of contact details. Change options
value in activity service.
|
|
Step 2: Change activities.hooks.js
We will do more than just query for the related contacts -
- Use
find
hook to$select
only some fields to include in the result - Allow feathers to eager load contact -
$eager: "contact"
- Sort results by
created_at (desc)
-$sort: { created_at: -1 }
- Filter activities by owner if the logged in user is not an admin -
if (!isAdmin(context)) query["user_id"] = context.params.user.id;
These changes are more or less self-explanatory - you can just see the super powers of feathers tumbling out one after the other.
|
|
If you don’t want to pass context
everywhere (e.g. isAdmin(context)
), you should totally checkout hooks-common
.
After these changes, it is time to get a different response when querying activities
-
GET http://localhost:3030/activities
|
|
You can create more than one record to test sort order and filter conditions.
Finis
That is it!
We now have the server-side of a totally real-world CRM application ready -
- Store users, contacts and activities
- Ability to register and login
- Secured data access by filtering activities by owner
If you are stuck anywhere, you could always check your code against the featherlight-crm repository.
Excited about this CRM application and can’t wait to start using it? Onwards to the next phase and developing a FeathersJS client application on Vue!