Get started with Feathers & React — Part 2

Monarch Maisuriya
JavaScript in Plain English
12 min readOct 31, 2020

--

Illustration by Undraw

In my previous article, I explained briefly why to choose React and Feathers to create a Web-App. Now in this article, we will set up a basic Web-App with React Client and Feathers Server configured with a REST API and authentication.

So let us get started.

First, we will create/setup the React App/Client and then the Feathers App/Server.

So basically, there are many ways to set up a React App, but we will use the most common and straightforward way using create-react-app.

Prerequisites: You need to have Node and npm (alternatively you can also use yarn instead of npm) installed globally in your system.

So let’s run the create-react-app in the terminal with npx (node.js package runner) to generate our Client.

// cd to the directorynpx create-react-app client

This will take a couple of minutes to finish.

Meanwhile let’s generate the Server, similar to React there is more than one way to create a Feathers App but again we will use the most common way — feathers-cli. Open up another tab in the terminal and type the following command.

Prerequisites: You need to have feathers-cli installed globally in your system.

// cd to the directoryfeathers generate app

The feathers-cli will begin a series of prompts that configures the server to your needs. The important ones are:

  1. The language preference (JavaScript/TypeScript) — Javascript
  2. The choice of a package manager (npm/yarn) — npm
  3. The API type (RESTful and/or real-time) — REST
  4. The choice of a testing library (Jest/Mocha + assert) — JEST
  5. Choose if we want authentication (creates an authentication service) — Yes
  6. The choice of a linting library (ESLint/StandardJS) — ESLint
  7. The choice of an authentication strategy (Local/OAuth) — Local
  8. The kind of service to use with authentication — users (can vary according to the use case)
  9. The choice of a database adapter for the authentication service — mongoose
  10. Specify the database connection according to the chosen adapter — mongodb://localhost:27017/server (you can also choose to use a Remote Connection)

That’s it. We have successfully created a client (React) and a server (Feathers) for our Web-App (which when combined makes the Web-App btw).

Now, go ahead and run the client and the server in separate terminals.

// cd to the respective directoriesnpm start (or yarn start)

You should see something like this.

Images created by Carbon

Now as both the projects (client & server) are up and running. Let us walk through the project directories see what the generators/CLIs have created and browse through the code within.

Project directories — ( src/ )

  • client
├── src├── App.css├── App.js├── App.test.js├── index.css├── index.js├── logo.svg├── reportWebVitals.js└── setupTests.js

Here the index.js is the root of the app, that holds the main App component, which brings us to the App.js. All of the different components for the app are configured into the App.js.

The internal routing of the app is also configured in the App.js. I will not go into the depths of how routing works, but routing is the navigation among views of various components in a React Application. Take a look here, if you wish to learn more about it.

The App.js is then inserted as a dependency into the index.js, which renders all the components and makes a single page application.

The CSS files consist of the styling for the app.

You can ignore the reportWebVitals.js and setupTests.js for now.

  • server
├── app.hooks.js├── app.js├── authentication.js├── channels.js├── index.js├── logger.js├── middleware│   └── index.js├── models│   └── users.model.js├── mongoose.js├── services├── index.js└── users├── users.class.js├── users.hooks.js└── users.service.js

Here, similar to the client index.js is the root of the server. It contains the code that will execute the app and start the server. The app.js holds the three crucial instances — feathers, express (RESTful part), and mongoose (data modelling for MongoDB) including all the configurations and instantiations for different services, modules, and dependencies. The app.hooks.js contains the before, after, and error hooks, these hooks run on every HTTP call of the app which means these hooks will run on every service.

Next, the authentication.js implements Local & JWT strategies. It registers these strategies and configures expressOauth in our Feathers App. The service is registered on ‘/authentication’ route.

Moving on, the channels.js have the event channels that determine which connected clients to send real-time events to and how the sent data should look like. It is used on a server with real-time transport (Socket.io or Primus) set up. But since we do not have that we can choose to ignore this.

Next is the logger.js which is a simple pluggable function created with winston to log the app events and actions. And the middleware/ which is used to register any express middleware within the Feathers App.

All the data models (for MongoDB) of the services are stored inside models/. They are automatically generated (you can modify them later). The mongoose.js consists of the client for the MongoDB database driver — mongoose.

Moving on to Services — the heart of Feathers. The services contain all the business logic for our Feathers App. The class.js is where we write business logic. The service.js configures the service path, instantiates the class, and creates a model. The hooks.js same as the app.hooks.js contains the before, after, and error hooks for the particular service. And the index.js is where all the services are registered into the Feathers App.

This is it for the overview of the project directories and the code within.

Now, we can move onto the next step. Connecting the client to the server.

But before we do that, let’s test our authentication API real quick. I normally use Postman, to test my APIs but you can use any REST Client you’d like.

So, fire up the REST Client and make a POST request with the credentials object (email and password) to “/users”.

url : http:localhost:3030/users// body type - JSONbody :{"email":"monarch@maisuriya.com", // your email"password":"maisuriya" // your password}

After you hit send you should receive a response similar to this

{"_id": "5f98effe9958b369f665215d","email": "monarch@maisuriya.com","createdAt": "2020-10-28T04:13:50.397Z","updatedAt": "2020-10-28T04:13:50.397Z","__v": 0}

Voila! This means we have successfully created a user in our system with our API. And the user has the above-mentioned properties.

Now let’s try authenticating the created user.

Open up another tab in the REST Client and make a POST request to “/authentication”. But this time we need to add an extra property to our credentials object — the strategy which in our case is — local.

url : http:localhost:3030/authentication// body type - JSONbody :{"strategy":"local", // the auth strategy we chose earlier"email":"monarch@maisuriya.com", // email used to create the user"password":"maisuriya" // password used to create the user}

Hit send and you should receive a response object similar to this.

{"accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6ImFjY2VzcyJ9.eyJpYXQiOjE2MDM4NTg5NTEsImV4cCI6MTYwMzk0NTM1MSwiYXVkIjoiaHR0cHM6Ly95b3VyZG9tYWluLmNvbSIsImlzcyI6ImZlYXRoZXJzIiwic3ViIjoiNWY5OGVmZmU5OTU4YjM2OWY2NjUyMTVkIiwianRpIjoiZDUyNzAzZjYtYTZhZS00NDgyLTg2MDYtODdlYTNmMzBjNzBlIn0.OMTAJO_cb9erx4SqHIfFUplQCy5a2CIS2-OfrATNqw4","authentication": {"strategy": "local"},"user": {"_id": "5f98effe9958b369f665215d","email": "monarch@maisuriya.com","createdAt": "2020-10-28T04:13:50.397Z","updatedAt": "2020-10-28T04:13:50.397Z","__v": 0}}

It has the access token (JWT), the user details, and the authentication strategy.

And there you have it, a fully functional authentication based REST API.

Did you see how easy it was to set this up? No hassle of manually setting up Passport and JWT and local strategy or sessions and cookies or configuring a database with any ORMs or ODMs. Feathers does this all on its own for us. Isn’t this amazing?

Now the next step. Connecting our React Client to our Feathers Server.

To do this the Feathers way (which in my opinion is pretty easy and the way to go with) we will need to install a few dependencies in the client. So open up the terminal in the React App directory and install the following dependencies with yarn (or npm).

yarn add @feathersjs/authentication-client @feathersjs/client @feathersjs/feathers @feathersjs/rest-client

Since we have added the required dependencies. Let’s write the actual code to connect the client to the server.

Create a new directory called ‘client’ inside src. And inside this directory create an index.js. And type in the following code.

Image created by Carbon

I am using axios as the HTTP Client. But you can use any other client you want, just add it as a dependency. Read more about it here.

So what does this piece of code do?

It is fairly simple. We are importing the feathers instance from the client, the auth instance from authentication-client, and the rest instance from rest-client.

First, we instantiate feathers(), then we instantiate rest() — pass the API_URL ( in our case — http:localhost:3030) as the parameter.

Note - I am using environment variables to store default information like the API_URL inside a .env at the root of the client/

Second, we configure the restClient in the feathersClient along with the HTTP Client of our choice. Then we configure the feathersClient with feathers-authentication.

Third, we set up the auth() in feathersClient with some typical options.

// stores the jwt in the browser's local storage
storage: window.localStorage,
// storage key for the jwt
storageKey: "feathers-react-jwt"

And this is it, we have successfully connected our React App to our Feathers Server. Now, we can use this feathersClient to make API calls to our server.

Now the closing step of this article.

Let’s create some simple React Components to compose a SignUp and a SignIn page, which will allow us to communicate with the server to create and authenticate users via a simple user interface.

Now I don’t want to create these components from scratch. So I am using Chris Bakley’s React Forms Starter Template, which has a couple of React Forms with decent styling.

I have also used react-router-dom as a dependency for internal routing in our app.

So after adding the code from the template to our client, the final App looks like this.

So what is happening here?

It is fairly simple. We have a landing page with two CTA buttons — SignUp and SignIn. On clicking either button, the app will navigate us to a different view.

  • Signup Form: It’s a simple React Component with two inputs and a submit button. So let’s take a look at the code.
Images created by Carbon

So what exactly is happening here? Let’s walk through it step-by-step.

First, we are importing the necessary dependencies like the useState hook and the feathersClient.

If you are not familiar with React Hooks, I suggest you take a look here, before moving forward.

So as I mentioned in my previous article, the state object is where you store property values that belong to the component. And since we are using React’s Functional Components, we will need to use hooks to carry out the same operations as a Class-Based Component.

Why I chose Functional Components over Class-Based? It is a whole another story. If you wish to understand it, you can read this article.

And we are producing a simple SignUp Component. Now since we will be using the credentials (email, password) from the user to create the user we will need to store them in a temporary local state in the component before we send it to the server, this is where the useState hook comes in. It allows us to create the local state of a component. So, I am creating a state object (calling it credentials) with email and password properties. I am also creating another state object - result to store the response we get from the server.

Let’s jump ahead to the JSX part of the component. We will understand the methods (handleEmailInputChange, handlePasswordInputChange, handleSubmit) simultaneously with it.

So, we have created a basic form with two inputs (email and password) and a submit button. Looks fairly simple, but what we need to focus on here are the two attributes of the inputs - value and onChange. Here the value attribute holds the value of the input, which we are bringing from the credentials object, which takes us to the next attribute onChange.

The onChange attribute calls the methods handleEmailInputChange or handlePasswordInputChange depending on the input and changes the value of the input as the value of the credentials object. Now let’s take a look at the general idea behind these two methods.

The methods take a parameter event , which is a javascript event.

// passing the credentials object
setCredentials((credentials) => ({
// spreading the credentials object
...credentials,
// changing the input (email/password) value
input: event.target.value,
}));

So, let’s move on to the submit button which when clicked will submit the form. Now that brings us to the very beginning where we created the form, the form has an attribute onSubmit which calls a method when the form is submitted. So let’s take a look at the method being called when we submit the form - handleSubmit.

Again this method takes a parameter event , which is a javascript event.

// e = event// prevent the default behaviour of the form on submit
e.preventDefault();
// A simple Try-Catch Blocktry {/* since this is an async method we are awaiting and then storing the response from the server in result */
const result = await feathersClient
.service('users').create(credentials);
/* The main reason why we used the feathersClient - it allows us to call the API in this simple manner :
clientInstance.service('service name/route').HTTPMethod({params}) */
/* using the set method to store the result from server in the result object we created earlier */
setResult(result);
} catch (error) {
/*
Throwing the error in a catch statement defeats its purpose. I did it for testing purposes only.
*/
throw Error(error);
}

In the Try-Catch the feathersClient is requesting the server to create a new user in the users service. On success, the server will return a response object which will be stored in the result object. And I am using the concept of conditional rendering and rendering the Link to the Signin Form only when the result object isn’t null or when the user has signed up. See that was easy!

  • Signin Form: Same as the Signup Form it’s a simple React Component with two inputs and a submit button. But let’s take a look at the code.
Images created by Carbon

So, as you can see the Signin Form is very similar to the Signup Form. There are only a few changes. Let’s go over these changes.

First, the logic within the handleSubmit method is changed. But it still takes the same parameters.

// e = event// prevent the default behaviour of the form on submit
e.preventDefault();
// A simple Try-Catch Blocktry {/* Feathers Client provides an authenticate() strategy for authentication. Using that you can just call that method with the appropriate params and Feathers will authenticate the user, and return a JWT with other details. But you can also make a request with the default syntax, like the one we discussed above in the SignUp Form */
await feathersClient.authenticate({strategy: 'local', ...credentials});
/* Now as I said, calling the authenticate() returns an object with JWT, so why are we calling the service again? Because I want the result object to work as a computed property, for our conditional render later, so I will let Feathers authenticate first and then request the response later */
const result = await feathersClient.get('authentication');
// Store the result
result ? setResult(result) : setResult(null);
} catch (error) {
/*
Throwing the error in a catch statement defeats its purpose. I did it for testing purposes only.
*/
throw Error(error);
}

Second, the conditional rendering at the end of the form is changed, instead of a Link, there is a Sign Out button with a callback of handleSignOut which makes a request to logout.

feathersClient.logout()
/* This method removes the access token from storage on the client. It also calls the remove method of the authentication service */

And that’s it we are done.

We have created a completely functional web app with the rudimentary setup and configurations. Kudos!!!

This is it for Part 2 of “Get started with Feathers & React”. Thanks for reading :)

Part 3: Customize the React App with Ant Design UI Library, implement State Management with Redux-Toolkit, and add other essential security features. Add authentication management to the Feathers Server.

Coming Soon. Stay tuned.

--

--

I am an inquisitive experimenter and I am very enthusiastic about technology. I tend to be half in the shell and half out.