How to Syncing Chatkit messages in the background in React native?

How to Syncing Chatkit messages in the background in React native?

Learn how to make Chatkit messages available offline while a React Native app is running in the background.

You will need Node, Yarn and React Native installed on your machine.

In this tutorial, you’ll learn how to make Chatkit messages available offline while a React Native app is running in the background.

Prerequisites

Knowledge of React and React Native is required to follow this tutorial. Knowledge of Redux is helpful but not required.

The following package versions are used. If you encounter any issues in compiling the app, try to use the following:

  • Node 11.2
  • Yarn 1.13
  • React Native 0.59

You’ll need a Chatkit app instance with the test token provider enabled. If you don’t know the basics of using Chatkit yet, be sure to check out the official docs. This tutorial assumes that you at least know how to create, configure, and inspect a Chatkit app instance.

Lastly, you’ll need an ngrok account for exposing the server to the internet.

Bootstrapping the app

As mentioned in the previous section, we will be adding an offline functionality on top of an existing React Native chat app. So in the repo, I’ve added a starter branch which already contains the chat code. Go ahead and clone it and switch to the branch:

 git clone https://github.com/anchetaWern/RNChatkitBackgroundSync
    cd RNChatkitBackgroundSync
    git checkout starter

Next, install and link the dependencies:

 yarn
    react-native eject
    react-native link react-native-gesture-handler
    react-native link react-native-config
    react-native link react-native-background-timer

An extra step is required by React Native Config for Android. Add the following to the android/app/build.gradle file:

apply from: "../../node_modules/react-native/react.gradle"
// add these:
apply from: project(':react-native-config').projectDir.getPath() + "/dotenv.gradle"

You also need to update your android/app/src/main/AndroidManifest.xml file to include the permission for accessing the network state. This is required by the React Native Offline package so it has access to the network state:

<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Adding offline functionality

Now we’re ready to start coding. First, we’ll update the route file to import and use the packages that we need for implementing offline functionality. Then we’ll add the reducers and action creators that are responsible for updating the store. The store will be automatically persisted using the Redux Persist package so we can actually implement the Redux store just like we normally do. Lastly, we’ll update the login and chat screen to use the action creators.

Route file

First, import the packages that we need. This includes React Native Offline for various offline utilities and components, Redux Saga for implementing the network event listener saga from React Native Offline, and Redux and React Redux for implementing the store, and Redux Persist for persisting the store in the local storage:

   // update: Root.js
import { persistStore, persistReducer } from "redux-persist";
import storage from "redux-persist/lib/storage";
import { PersistGate } from "redux-persist/integration/react";
import createSagaMiddleware from "redux-saga";

import { Provider } from "react-redux";
import { createStore, combineReducers, applyMiddleware } from "redux";

import {
  ReduxNetworkProvider,
  reducer as network,
  createNetworkMiddleware
} from "react-native-offline";

Next, import the Chat reducer and the saga for watching when the device goes either offline or online:

  import ChatReducer from './src/reducers/ChatReducer';
import { watcherSaga } from './src/sagas';

const sagaMiddleware = createSagaMiddleware();
const networkMiddleware = createNetworkMiddleware();

Next, add the Redux Persist config. This allows us to specify the storage to use. In this case, we’re using the AsyncStorage implementation of Redux Persist. The key for the root of the storage should be root, while the key for the Chat reducer is chat. This should be the same as the key you provide to the Chat reducer when you call combineReducers (we’ll get to that later):

 const persistConfig = {
key: "root",
storage
};

const chatPersistConfig = {
  key: "chat",
  storage: storage
};

const rootReducer = combineReducers({
  chat: persistReducer(chatPersistConfig, ChatReducer),
  network
});

const persistedReducer = persistReducer(persistConfig, rootReducer)

Next, persist the store and run the watcher saga:

 const store = createStore(
persistedReducer,
applyMiddleware(networkMiddleware, sagaMiddleware)
);
let persistor = persistStore(store);

sagaMiddleware.run(watcherSaga);

Lastly, wrap the AppContainer with the <PersistGate> component. This delays the rendering of the main app container until the persisted state is retrieved from local storage and saved to the Redux store. On the other hand, <ReduxNetworkProvider> is the component exposed by React Native Offline for the purpose of passing down the network state as props to the children of the app container. This allows us to selectively render various components based on the network status (you’ll see this in action in the Login screen later):

 const RootStack = createStackNavigator(
// ..
);

const AppContainer = createAppContainer(RootStack);

// update this:
class Router extends Component {
  render() {
    return (
      &lt;Provider store={store}&gt;
        &lt;PersistGate loading={null} persistor={persistor}&gt;
          &lt;ReduxNetworkProvider&gt;
            &lt;AppContainer /&gt;
          &lt;/ReduxNetworkProvider&gt;
        &lt;/PersistGate&gt;
      &lt;/Provider&gt;
    );
  }
}

export default Router;

Action types

Now we move on to adding the action types. These are the type of actions that we will be dispatching from the Login and Chat screens to update the store:

// create: src/actions/types.js export const SET_USER = "set_user"; // for setting the username of the current user export const SET_FRIEND = "set_friend"; // for setting the friend's username export const SET_ROOM = "set_room"; // for setting the object containing the name and ID of the Chatkit room export const PUT_MESSAGE = "put_message"; // for pushing a single message into the store export const SET_MESSAGES = "set_messages"; // for setting the messages in the store export const PUT_OLDER_MESSAGES = "put_older_messages"; // for prepending messages in the messages that are currently in the store
Action creator

Next, create the action creators file. These are the functions that we will be dispatching when we want to update various parts of the store. Each function returns an object containing the type of the action and the payload which will be passed by the caller:

 // create: src/actions/index.js
import {
SET_USER,
SET_FRIEND,
SET_ROOM,
PUT_MESSAGE,
SET_MESSAGES,
PUT_OLDER_MESSAGES
} from "./types";

export const setUser = user =&gt; {
  return {
    type: SET_USER,
    user
  }
};

export const setFriend = friend =&gt; {
  return {
    type: SET_FRIEND,
    friend
  }
};

export const setRoom = room =&gt; {
  return {
    type: SET_ROOM,
    room
  }
};

export const putMessage = message =&gt; {
  return {
    type: PUT_MESSAGE,
    message
  };
};

export const setMessages = messages =&gt; {
  return {
    type: SET_MESSAGES,
    messages
  };
};

export const putOlderMessages = messages =&gt; {
  return {
    type: PUT_OLDER_MESSAGES,
    messages
  };
};

ChatReducer

Next, create the Chat Reducer. This is the one responsible for describing how the store will change based on the actions that it receives:

  // create: src/reducers/ChatReducer.js
import {
SET_USER,
SET_FRIEND,
SET_ROOM,
PUT_MESSAGE,
SET_MESSAGES,
PUT_OLDER_MESSAGES
} from "../actions/types";

const INITIAL_STATE = {
  user: null,
  friend: null,
  room: null,
  messages: []
};

export default (state = INITIAL_STATE, action) =&gt; {
  switch (action.type) {

    case SET_USER:
      return { ...state, user: action.user };

    case SET_FRIEND:
      return { ...state, friend: action.friend };

    case SET_ROOM:
      return { ...state, room: action.room };

    case PUT_MESSAGE:
      const updated_messages = [action.message].concat(state.messages);
      return { ...state, messages: updated_messages };

    case SET_MESSAGES: // initialization, refresh
      return { ...state, messages: action.messages };

    case PUT_OLDER_MESSAGES: // load previous messages
      const current_messages = [...state.messages];
      const older_messages = action.messages.reverse();
      const with_old_messages = current_messages.concat(older_messages);

      return {
        ...state,
        messages: with_old_messages
      };

    default:
      return state;
  }
};

To consolidate things, we bring the Chat Reducer and the Network Reducer provided by React Native Offline together:

 // create: src/reducers/index.js
import { combineReducers } from "redux";
import ChatReducer from "./ChatReducer";
import { reducer as network } from "react-native-offline";

export default combineReducers({
  chat: ChatReducer,
  network
});

Lastly, create the saga for watching the network (when it goes offline or online):

// create: src/sagas/index.js
import { networkSaga } from "react-native-offline";
import { fork, all } from "redux-saga/effects";

export function* watcherSaga() {
  yield all([
    fork(networkSaga, {
      timeout: 5000, // 5-second timeout for retrieving the network status
      checkConnectionInterval: 1000 // check network status every 1 second
    })
  ]);
}

Login screen

As mentioned earlier, the chat functionality has already been laid out. All we have to do in each of the screens is to dispatch the action creators instead of setting data into the component’s state.

In the Login screen, start by adding the ActivityIndicator:

// src/screens/Login.js import { View, Text, TextInput, TouchableOpacity, ActivityIndicator } from "react-native"; // add ActivityIndicator

Next, import the action creators:

import { connect } from 'react-redux'; import { setUser, setFriend } from '../actions';

Once the component is mounted, we check if the user is online. If they’re not, then we automatically navigate them to the Chat screen. This is because we don’t really have the capability to authenticate them when they’re offline. So we just log in the last user who used the app:

 componentDidMount() {
const { isConnected, user, friend, messages } = this.props;
if (user && !isConnected) {
this.props.navigation.navigate("Chat", {
user_id: user.id,
username: user.name,
friends_username: friend
});
}
}
Note: Since we’re now using Redux, all the general data used by the app is now passed down as a prop. Though this doesn’t happen automatically because we first have to connect the component by means of the connect method provided by React Redux.

Next, show a loading animation if the user is offline. We only show the login form if the user is online. This means that the login screen will simply show an infinite loading animation if a user didn’t log in previously:

 render() {
const { isConnected, user, friend } = this.props;
const { username, friends_username } = this.state;

  return (
    &lt;View style={styles.wrapper}&gt;
    {
      !isConnected &amp;&amp;
      &lt;ActivityIndicator
        size="small"
        color="#0064e1"
        style={styles.loader}
      /&gt;
    }

    {
      isConnected &amp;&amp;
      &lt;View style={styles.container}&gt;

        &lt;View style={styles.main}&gt;
          &lt;View style={styles.fieldContainer}&gt;
            &lt;Text style={styles.label}&gt;Enter your username&lt;/Text&gt;
            &lt;TextInput
              style={styles.textInput}
              onChangeText={username =&gt; this.setState({ username })}
              value={username}
            /&gt;
          &lt;/View&gt;

          &lt;View style={styles.fieldContainer}&gt;
            &lt;Text style={styles.label}&gt;Enter friend's username&lt;/Text&gt;
            &lt;TextInput
              style={styles.textInput}
              onChangeText={friends_username =&gt; this.setState({ friends_username })}
              value={friends_username}
            /&gt;
          &lt;/View&gt;

          {!this.state.is_loading &amp;&amp; (
            &lt;TouchableOpacity onPress={this.enterChat}&gt;
              &lt;View style={styles.button}&gt;
                &lt;Text style={styles.buttonText}&gt;Login&lt;/Text&gt;
              &lt;/View&gt;
            &lt;/TouchableOpacity&gt;
          )}

          {this.state.is_loading &amp;&amp; (
            &lt;Text style={styles.loadingText}&gt;Loading...&lt;/Text&gt;
          )}
        &lt;/View&gt;
      &lt;/View&gt;
    }
    &lt;/View&gt;
  );
}

Next, update the enterChat method so that it saves the details of the current user as well as the username of their friend to the store. This is the one that’s filling the value for user when the component is mounted:

enterChat = async () => {
// ...
const user_id = stringHash(username).toString();
const { setUser, setFriend } = this.props; // add this

  this.setState({
    is_loading: true
  });  

  if (username &amp;&amp; friends_username) {
    // add these:
    setUser({
      id: user_id,
      name: username
    });
    setFriend(friends_username);

    // ...
  }
}

Lastly, add the mapStateToProps and mapDispatchToProps. The former allows us to extract specific data from the store and make it available as props. While the latter allows us to create functions that are used for dispatching action creators and make it available as props:

  const mapStateToProps = ({ network, chat }) => {
const { isConnected } = network;
const { user, friend, messages } = chat;
return {
isConnected,
user,
friend,
messages
};
};

const mapDispatchToProps = dispatch =&gt; {
  return {
    setUser: user =&gt; {
      dispatch(setUser(user));
    },
    setFriend: friend =&gt; {
      dispatch(setFriend(friend));
    }
  };
};

// connect the component to the store
export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Login);

Chat screen

Let’s proceed to updating the Chat screen. Start by importing the additional modules that we need, as well as the action creators:

 // src/screens/Chat.js
// ...
import { View, ActivityIndicator, AppState } from "react-native"; // add AppState
import Config from "react-native-config";

// add these
import { connect } from "react-redux";
import BackgroundTimer from "react-native-background-timer"; // for periodically executing specific code 

import {
  setRoom,
  setMessages,
  putMessage,
  putOlderMessages
} from '../actions';

// ...

Next, we initialize the value of appstate. Here, we’re using React Native’s built-in module to check the app state. The app state can either be active, background, or inactive. We make use of appstateto determine whether the app is in the background or not at any given time:

 state = {
// ...
show_load_earlier: false,
app_state: AppState.currentState
};

Once the component is mounted, we check if the user is offline and immediately initialize the Chat screen if they are. This is because, by default, the Chat screen displays an animated loader until Chatkit has been initialized. We can’t really initialize Chatkit when the user is offline, so we immediately set is_initialized to true so that the chat UI will be displayed. Aside from that, we also add an event listener to the app state. We need to do this because the value of AppState.currentStateis only initialized once so it doesn’t really reflect the current app state:

componentDidMount() {
const { isConnected, setMessages, putMessage, messages } = this.props;
AppState.addEventListener('change', this._handleAppStateChange);

  if (isConnected) { // wrap this.enterChat with this condition
    this.enterChat();
  }

  // next: add code for background sync

  // add these:
  if (!isConnected) {
    this.setState({
      is_initialized: true
    });
  }
}

Next, we add the code for running a task in the background. This uses the React Native Background Timer package to periodically run specific code even if the app goes into the background. Note that the function that we supply to the runBackgroundTimer method is actually executed even when the app is in the foreground. This is where checking for the current app state comes in. If it’s not active, then that’s the time we execute our messages syncing code. In the server, later on, we’ll add a new route called /messages which is responsible for returning an array of messages that were added after the initial_id supplied:

 BackgroundTimer.runBackgroundTimer(() => {
const { app_state } = this.state;

  if (isConnected &amp;&amp; app_state !== 'active') {
    // fetch messages from the server
    console.log('app went to background, now getting messages from the server...');

    const latest_message_id = Math.max(
      ...messages.map(m =&gt; parseInt(m._id))
    );

    axios.get(`${CHAT_SERVER}/messages`, {
      params: {
        room_id: this.room_id,
        initial_id: latest_message_id
      }
    })
    .then((response) =&gt; {

      // next: add code for updating the store with the new messages  

    })
    .catch((err) =&gt; {
      console.log("error occurred: ", err);
    });
  }
}, 60000);

Here’s the code for putting the new messages into the store. Note that the individual message objects are different from the ones that you’re getting from Chatkit’s frontend API. I’ve also added a sample message object below:

  const { messages } = response.data;
messages.reverse().forEach((msg) => {
/*
{
id:101230436,
user_id:'193417020',
room_id:'31068818',
parts:[
{
content:'hello!',
type:'text/plain'
}
],
created_at:'2019-04-08T08:03:33Z',
updated_at:'2019-04-08T08:03:33Z'
}
*/

  const text = msg.parts.find(part =&gt; part.type === 'text/plain').content;
  const message = {
    _id: msg.id,
    text: text,
    createdAt: msg.created_at,
    user:{
      _id: msg.user_id,
      avatar: "https://png.pngtree.com/svg/20170602/0db185fb9c.png"
    }
  }
  putMessage(message);
});

Next, add the code for listening for when the app state changes. Aside from updating the state, we also need to disconnect the current user from Chatkit if the app goes to the background, and connect them when the app goes in the foreground. We need to do this to ensure that the existing Chatkit connections don’t interfere with our code for syncing the messages in the background:

 _handleAppStateChange = (nextAppState) => {
if (nextAppState !== 'active' && this.currentUser) {
this.currentUser.disconnect();
} else if (nextAppState === 'active') {
this.enterChat();
}

  this.setState({
    app_state: nextAppState
  });
};

Next, update the enterChat method to set Chatkit’s logger to use console.log for every type of log. This helps us prevent React Native’s “Red Screen of Death” from showing when Chatkit couldn’t sync messages while the user is offline. From here, we also dispatch the actions for setting the current room and setting the messages so that the store is updated:

 enterChat = async () => {

  const { setRoom, setMessages } = this.props;

  try {
    if (!this.chatManager) {
      this.chatManager = new ChatManager({
        instanceLocator: CHATKIT_INSTANCE_LOCATOR_ID,
        userId: this.user_id,
        tokenProvider: new TokenProvider({ url: CHATKIT_TOKEN_PROVIDER_ENDPOINT }),
        // add these:
        logger: {
          verbose: console.log,
          debug: console.log,
          info: console.log,
          warn: console.log,
          error: console.log,
        }
      });

      // ...

      this.room_id = room.id.toString();

      // add this
      setRoom({
        id: this.room_id,
        name: this.room_name
      });

      // ...
    }

    setMessages([]); // add this

    // ...

  } catch (err) {
    console.log("error with chat manager: ", err);
  }
}

Note: setMessages will reset the messages array in the store. This means any of the old messages that were previously loaded via Chatkit’s fetchMessages method will also be deleted. If you want to retain old messages, you will need to update the Chat reducer.

Next, update the render method to load the messages from props instead of from the state. The rest of the code remains intact:

 render() {
const { is_initialized, show_load_earlier } = this.state;
const { messages } = this.props; // add this

  return (
    &lt;View style={styles.container}&gt;
      {(!is_initialized) &amp;&amp; (
        &lt;ActivityIndicator
          // ...
        /&gt;
      )}

      {is_initialized &amp;&amp; (
        &lt;GiftedChat
          // ...
        /&gt;
      )}
    &lt;/View&gt;
  );
}

Next, update the onSend method to check if the user is online. The user should only be able to send a message if they are online:

 onSend([message]) {
const { isConnected } = this.props;

  if (isConnected) {
    // ...
  }
}

Next, update the onReceive method to dispatch the putMessage action when a new message is received:

 onReceive = async (data) => {
const { messages, putMessage } = this.props;
const { message } = await this.getMessage(data);

  putMessage(message);

  // ...
}

Next, update the method for loading older messages so it dispatches the putOlderMessages action when all the older messages are added to the earlier_messages array. Also, make sure to get the messages from props instead of from the state:

loadEarlierMessages = async () => {

  const { putOlderMessages, isConnected, messages } = this.props; // add this

  if (isConnected) {
    // ...

    const earliest_message_id = Math.min(
      ...messages.map(m =&gt; parseInt(m._id)) // update this
    );

    try {
      let messages = await this.currentUser.fetchMessages({
         // ...
      });

      if (!messages.length) {
        // ...
      }

      let earlier_messages = [];
      await this.asyncForEach(messages, async (msg) =&gt; {
        let { message } = await this.getMessage(msg);
        earlier_messages.push(message);
      });

      putOlderMessages(earlier_messages); // add this

    } catch (err) {
      // ...
    }
  }

  await this.setState({
    is_loading: false
  });
}

Lastly, connect the component to the store:

 const mapStateToProps = ({ network, chat }) => {
const { isConnected } = network;
const { user, messages } = chat;
return {
isConnected,
user,
messages
};
}

const mapDispatchToProps = dispatch =&gt; {
  return {
    setRoom: room =&gt; {
      dispatch(setRoom(room));
    },
    setMessages: messages =&gt; {
      dispatch(setMessages(messages));
    },
    putMessage: message =&gt; {
      dispatch(putMessage(message));
    },
    putOlderMessages: older_messages =&gt; {
      dispatch(putOlderMessages(older_messages));
    }
  };
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(Chat);

Update the server

The final thing we need to do to implement background sync is to add the /messages route to the server. As mentioned earlier, this is responsible for returning the messages that were added after the initial_id passed into the request:

app.get("/messages", async (req, res) => {
const { room_id, initial_id } = req.query;
try {
const messages = await chatkit.fetchMultipartMessages({
roomId: room_id,
limit: 10,
initialId: initial_id // only fetch messages after this message ID
});

    res.send({ messages });

  } catch (err) {
    console.log("error fetching messages: ", err);
  }
});

Running the app

Now we’re ready to run the app. But before doing so, make sure to update the .env and server/.envfiles with your Chatkit credentials.

Once that’s done, you can now install the dependencies and run the app:

 cd server
yarn
yarn start
./ngrok http 5000

Lastly, update your app/screens/Login.js and app/screens/Chat.js file with your ngrok HTTPS URL and then run the app:

 react-native run-android react-native run-ios

To test the functionality we just implemented, log in to the app and then go to your home screen (or open another app) so that the React Native app goes to the background. Once the app is in the background, you can send messages to the user you logged in via the Chatkit console or running the app on another device. Those messages should sync to the app and saved in device’s the local storage. This allows the user to view those messages when they pull the app back to the foreground at a later time even when they’re offline. This is as opposed to simply pulling the messages when the app goes to the foreground, because if they are offline at the time the app goes to the foreground then there would be no new messages available to read.

Conclusion and next steps

That’s it! In this tutorial, you learned how to make Chatkit messages available offline while a React Native app is running in the background.

The app we created is fairly limited in its message syncing features though. Because as soon as the user closes the app or the device goes into idle state, the syncing is also effectively stopped.

As next steps, you can try implementing the following so the app continually syncs messages even if the device goes idle or the user closes the app:

  • Push notifications - try implementing Push notifications for every message that’s received, but not when the user is currently using the app. You’ll need a server to implement this. First, you need to find a way to determine whether a specific user is currently using the app. Next, you can use Chatkit Webhooks to have your server listen to messages as they are sent. From there, you can send Push notifications to the receiver if the app isn’t currently in the foreground. You can use Pusher Beams or Firebase Cloud Messaging for that.
  • Background tasks - another option is to run a background task in the app. This will be responsible for syncing the messages at a specific interval. Reid Mayo has written a tutorial on this: Easy OS Background Tasks in React Native. There are also various packages that implement background jobs in one way or another: React Native Background Job, React Native Background Geolocation. Those might help you figure out how to continually run the app’s code even if the app is closed. But as all background jobs go, they’re not really friendly on the battery.

Top Vue.js Developers in USA

Top Vue.js Developers in USA

Vue.js is an extensively popular JavaScript framework with which you can create powerful as well as interactive interfaces. Vue.js is the best framework when it comes to building a single web and mobile apps.

We, at HireFullStackDeveloperIndia, implement the right strategic approach to offer a wide variety through customized Vue.js development services to suit your requirements at most competitive prices.

Vue.js is an open-source JavaScript framework that is incredibly progressive and adoptive and majorly used to build a breathtaking user interface. Vue.js is efficient to create advanced web page applications.

Vue.js gets its strength from the flexible JavaScript library to build an enthralling user interface. As the core of Vue.js is concentrated which provides a variety of interactive components for the web and gives real-time implementation. It gives freedom to developers by giving fluidity and eases the integration process with existing projects and other libraries that enables to structure of a highly customizable application.

Vue.js is a scalable framework with a robust in-build stack that can extend itself to operate apps of any proportion. Moreover, vue.js is the best framework to seamlessly create astonishing single-page applications.

Our Vue.js developers have gained tremendous expertise by delivering services to clients worldwide over multiple industries in the area of front-end development. Our adept developers are experts in Vue development and can provide the best value-added user interfaces and web apps.

We assure our clients to have a prime user interface that reaches end-users and target the audience with the exceptional user experience across a variety of devices and platforms. Our expert team of developers serves your business to move ahead on the path of success, where your enterprise can have an advantage over others.

Here are some key benefits that you can avail when you decide to hire vue.js developers in USA from HireFullStackDeveloperIndia:

  • A team of Vue.js developers of your choice
  • 100% guaranteed client satisfaction
  • Integrity and Transparency
  • Free no-obligation quote
  • Portal development solutions
  • Interactive Dashboards over a wide array of devices
  • Vue.js music and video streaming apps
  • Flexible engagement model
  • A free project manager with your team
  • 24*7 communication with your preferred means

If you are looking to hire React Native developers in USA, then choosing HireFullStackDeveloperIndia would be the best as we offer some of the best talents when it comes to Vue.js.

Top 7 Most Popular Node.js Frameworks You Should Know

Top 7 Most Popular Node.js Frameworks You Should Know

Node.js is an open-source, cross-platform, runtime environment that allows developers to run JavaScript outside of a browser. In this post, you'll see top 7 of the most popular Node frameworks at this point in time (ranked from high to low by GitHub stars).

Node.js is an open-source, cross-platform, runtime environment that allows developers to run JavaScript outside of a browser.

One of the main advantages of Node is that it enables developers to use JavaScript on both the front-end and the back-end of an application. This not only makes the source code of any app cleaner and more consistent, but it significantly speeds up app development too, as developers only need to use one language.

Node is fast, scalable, and easy to get started with. Its default package manager is npm, which means it also sports the largest ecosystem of open-source libraries. Node is used by companies such as NASA, Uber, Netflix, and Walmart.

But Node doesn't come alone. It comes with a plethora of frameworks. A Node framework can be pictured as the external scaffolding that you can build your app in. These frameworks are built on top of Node and extend the technology's functionality, mostly by making apps easier to prototype and develop, while also making them faster and more scalable.

Below are 7of the most popular Node frameworks at this point in time (ranked from high to low by GitHub stars).

Express

With over 43,000 GitHub stars, Express is the most popular Node framework. It brands itself as a fast, unopinionated, and minimalist framework. Express acts as middleware: it helps set up and configure routes to send and receive requests between the front-end and the database of an app.

Express provides lightweight, powerful tools for HTTP servers. It's a great framework for single-page apps, websites, hybrids, or public HTTP APIs. It supports over fourteen different template engines, so developers aren't forced into any specific ORM.

Meteor

Meteor is a full-stack JavaScript platform. It allows developers to build real-time web apps, i.e. apps where code changes are pushed to all browsers and devices in real-time. Additionally, servers send data over the wire, instead of HTML. The client renders the data.

The project has over 41,000 GitHub stars and is built to power large projects. Meteor is used by companies such as Mazda, Honeywell, Qualcomm, and IKEA. It has excellent documentation and a strong community behind it.

Koa

Koa is built by the same team that built Express. It uses ES6 methods that allow developers to work without callbacks. Developers also have more control over error-handling. Koa has no middleware within its core, which means that developers have more control over configuration, but which means that traditional Node middleware (e.g. req, res, next) won't work with Koa.

Koa already has over 26,000 GitHub stars. The Express developers built Koa because they wanted a lighter framework that was more expressive and more robust than Express. You can find out more about the differences between Koa and Express here.

Sails

Sails is a real-time, MVC framework for Node that's built on Express. It supports auto-generated REST APIs and comes with an easy WebSocket integration.

The project has over 20,000 stars on GitHub and is compatible with almost all databases (MySQL, MongoDB, PostgreSQL, Redis). It's also compatible with most front-end technologies (Angular, iOS, Android, React, and even Windows Phone).

Nest

Nest has over 15,000 GitHub stars. It uses progressive JavaScript and is built with TypeScript, which means it comes with strong typing. It combines elements of object-oriented programming, functional programming, and functional reactive programming.

Nest is packaged in such a way it serves as a complete development kit for writing enterprise-level apps. The framework uses Express, but is compatible with a wide range of other libraries.

LoopBack

LoopBack is a framework that allows developers to quickly create REST APIs. It has an easy-to-use CLI wizard and allows developers to create models either on their schema or dynamically. It also has a built-in API explorer.

LoopBack has over 12,000 GitHub stars and is used by companies such as GoDaddy, Symantec, and the Bank of America. It's compatible with many REST services and a wide variety of databases (MongoDB, Oracle, MySQL, PostgreSQL).

Hapi

Similar to Express, hapi serves data by intermediating between server-side and client-side. As such, it's can serve as a substitute for Express. Hapi allows developers to focus on writing reusable app logic in a modular and prescriptive fashion.

The project has over 11,000 GitHub stars. It has built-in support for input validation, caching, authentication, and more. Hapi was originally developed to handle all of Walmart's mobile traffic during Black Friday.