Ghost Themes
SimpleChat.Support - Open Source Live Chat App
0%
React with Webpack + Meteor as a backend
words - read.

React with Webpack + Meteor as a backend

I like to work with Meteor and if you read my blog you probably know about it. But sometimes, in my opinion, Meteor seems to be a little bit overcomplicated in its design principles and build system. I mean, I like to work with Webpack, React and all that stuff because I have unlimited control over it. It’s simple to replace parts of it and it’s really intuitive. And with Meteor, hmm, you can't even integrate PostCSS properly. I know that some of you will say – so what, I doesn't care about PostCSS. It isn't important. Ok, I agree, but this is just an example of a JavaScript tool which should be very simple to integrate with any JavaScript stack out there. In fact, it is, but not with Meteor.

In this article I want to show you how you can use a separated client app based on Webpack, React and Redux with connected real-time backend provided by Meteor. I'll link a repository with the demo app, so you'll be able to learn by reading the code.

What we'll cover here is:

  1. Why do I need to do this? What are the benefits?
  2. DDP protocol and Asteroid introduction.
  3. React / Redux / Webpack / Meteor boilerplate.

Why do I need to do this? What are the benefits?

First of all of course you don't need to do this. In fact, I haven't built any app based on this architecture, but I really want to try. Here's why.

Lately I work with React and Webpack. I love how flexible this workflow is. I can use all JavaScript libraries out there. I can build my own build system. I can provide Hot Module Replacement and adjust all performance stuff as I want. I can use preprocessors, PostCSS and other cool stuff like React CSS Modules. Finally, I can use my custom Mocha configuration for the testing environment with stuff like Enzyme or JSDOM. What is also important is that I can use all possible Babel plugins and configurations. This isn't possible in Meteor. You can't even use .babelrc file without community based packages (this will change soon). Webpack 2 brings Tree-shaking and better ES6 modules support too. There are also educational pros. All tutorials on the Internet are about using some tools with Webpack. There is a ton of materials about it. Meanwhile, there isn't even any good documentation for Meteor’s build system out there.

Ok, so enough complaining about Meteor's build system. Let's see the cons of this approach. First of all, you need to split your code into two separated apps. One - the client app - could be just some static files (html, css, js) and the second app - the server backend - should be a Meteor app hosted somewhere. The second thing is that it is much simpler to start with only Meteor. You just need to create a new project and all basic stuff is preconfigured for you. This is a good place to quote Sashko Stubailo - a core developer at MDG:

Of course I agree with that, but this might not be a problem anymore if we use some kind of boilerplate. You'll read about that kind of boilerplate later.

Ok, another question is - so why do I need Meteor for my backend if I just use it like some kind of API endpoints? Why not Express, Feathers, Hapi, Horizon or any other backend? And my simple answer would be - because I really know Meteor and the backend configuration with it is fast as hell. I just need one file and a couple lines of code. That’s why. You'll see the code later.

Let's sum up.

If you like working with only basic front-end stuff like a simple preprocessor and a standard preconfigured Babel transpiler and you don't care about that kind of stuff too much, you can use Meteor as is. It has a rather good React integration and testing stuff is much better now. But when you want some more configuration options on the front-end like CSS Modules, PostCSS, Babel plugins, elastic code splitting and unlimited performance configuration options, this approach could be for you.

It is worth mentioning that there is a community based project called The Reactive Stack which integrates Webpack into Meteor. Moreover, the Webpack configuration in this case is even simpler because you just need to add some Meteor packages to configure Webpack. I encourage you to check it out.

My approach is quite different. I just use two apps. A Meteor based backend app and a front-end Webpack based app which will be bundled to static files. So the benefit is that you don't modify the Meteor app, you can just do most of your work in the Webpack based app.

Two apps… the first one could be a static website, the second one is a Meteor backend app without front-end stuff. So, you’re probably wondering how they communicate with each other. The answer is - through DDP - Distributed Data Protocol. Let's see what it is exactly.

DDP protocol and Asteroid introduction.

The DDP is something like REST, but for WebSockets. The protocol defines some standards in communication between the server and client. It defines some JSON messages over WebSocket which are written down in the specification. In Meteor core, all data transportation relies on DDP, at least at the moment. There are ongoing works in MDG team which probably add more data layers to the Meteor stack in the future.

So, in simple words, our two apps will talk to each other using the WebSockets connection, and they will talk using a messages format which is strictly specified. The DDP protocol was originally developed by the Meteor team, but nothing in it is Meteor specific. Moreover, the client and server parts of the Meteor app don't know about each other. They just speak the same language. What it means is that we can communicate with the Meteor backend using some kind of DDP client. There are plenty of DDP clients out there. You can find clients for PHP or even ObjectiveC language. We will use a JavaScript DDP client called Asteroid. It has a very nice API. I really like it. In the end, our front-end app will be just a bunch of static files. So we need a JavaScript client which will be able to run in the browser to connect to the Meteor backend.

Asteroid allows you to connect to the DDP endpoint. In our case, it will be our Meteor app. With Asteroid, you can make calls to the remote methods or you can just subscribe to the remote publications. See an excellent usage example here. In most cases, you would like to do both.

Ok, let's dive in and read some code.

React / Redux / Webpack / Meteor boilerplate.

A while ago I prepared a React boilerplate which uses Redux and is based on Webpack. It also uses cool front-end stuff like PostCSS, CSS Modules, etc. It has a configured testing environment. At the end, you can just bundle your React app to the static files and host it anywhere. You can find the boilerplate here: https://github.com/juliancwirko/react-boilerplate.

Everything is fine, but you probably see one missing part here. We don't have any backend for this kind of app. This is where Meteor comes in. I was pleased with my React workflow, but sometimes I need a backend. In most cases, I use only Meteor when I want to build something with backend, but I thought that connecting my React boilerplate with Meteor as a backend could work quite well.

So, here it is. This is my Meteor backend:

const Todo = new Meteor.Collection('todo');

Meteor.publish('todo', function () {return Todo.find()});

Meteor.methods({  
    getTodo(id) {return Todo.findOne(id)},
    getTodos() {return Todo.find().fetch()},
    addTodo(message) {return Todo.insert({message: message})},
    removeTodo(id) {return Todo.remove({_id: id})},
    editTodo(id, finished) {
        return Todo.update({_id: id}, {$set: {finished: finished}});
    }
});

Todo.deny({  
    insert() {return true;},
    update() {return true;},
    remove() {return true;}
});

That's it. How awesome is that? Only one file and a couple lines of code. I know, it isn't secure enough and doesn’t have user accounts. But still, it has all that Todo app needs on the backend.

You can find all files for the demo Todo app here: https://github.com/juliancwirko/react-redux-webpack-meteor. There are two folders, each of them is a separate application which you can run. As you can see above, our Meteor app is very simple. This is just one file located in the server folder in the meteor-backend app. We just create a collection for our Todo items and then we can create publications (if we really need realtime data transportation) for it and some methods. You can read more about each part in the Official Meteor Guide I won’t cover it here.

Let's clone the repo with two apps in it:

$ git clone https://github.com/juliancwirko/react-redux- webpack-meteor.git

Let's run our Meteor backend app:

$ cd react-redux- webpack-meteor/meteor- backend
$ npm install
$ meteor -p 9000

We run it on port 9000 because our front-end app will run on port 3000 and will search for the backend app on port 9000.

This is basically all we need. Our backend is running so we can take a look at the front-end React app.

You'll find the front-end app in the react-client-app folder in the repo. As you can see, we have here a copy of the React boilerplate.

Let's run our React/Webpack client app:

$ cd react-redux-webpack-meteor/react-client-app
$ npm install
$ npm start

Remember that your Meteor backend app should be still running at port 9000, so run the client app in another terminal window.

When you go to localhost:3000 in the browser, you should see the Todo demo app. It should be connected to the running Meteor backend app through WebSockets using DDP protocol.

Let's see some code from the React app. You can read the Webpack configuration code in three files: webpack.config.js, webpack.prod.config.js, webpack.test.config.js. We use different configurations for different environments - dev, production and testing.

Our React app is based on React Router. In fact, we don't need it here, but it is good to have it configured in the boilerplate, just in case you want some subpages in the future. So, when you go to the app folder which is the main folder here, you'll see that we have our main App.js file here which is a starting point. There is a Router configuration and also the Redux store is passed down here.

import React from 'react';  
import ReactDOM from 'react-dom';  
import { Router, browserHistory } from 'react-router';  
import { Provider } from 'react-redux';  
import routes from './routes';  
import store from './store';

ReactDOM.render(  
  <Provider store={store}>
    <Router routes={routes} history={browserHistory} />
  </Provider>
, document.getElementById('app'));

Yes we use Redux here. Again, I won't write about Redux as there are many articles about it on the Internet. The documentation is quite impressive too.

What you need to know is that we integrate Redux actions to be able to call our remote methods configured in the backend Meteor app. Go and check the client/app/components/Todo/TodoActions.js file and client/app/components/Todo/TodoAsyncActions.js to see how it is done. You can also check the client/app/components/Todo/TodoReducers.js file.

Our DDP connection is configured in the app/common/asteroid.js file. As you can see, we just use Asteroid's API to connect to the Meteor backend app. Here we can (but we don't have to) subscribe to the remote data publications and we can configure our listeners. This is done by:

import { createClass } from 'asteroid';  
import { setLoggedUser, unsetLoggedUser } from '../components/Login/LoginActions';  
import { addTodo, removeTodo, editTodo } from '../components/Todo/TodoActions';  
import store from '../store';

const Asteroid = createClass();  
// Connect to a Meteor backend
const asteroid = new Asteroid({  
  endpoint: 'ws://localhost:9000/websocket',
});

// if you want realitme updates in all connected clients
// subscribe to the publication
asteroid.subscribe('todo');  
asteroid.subscribe('user');

asteroid.ddp.on('added', (doc) => {  
  // we need proper document object format here
  if (doc.collection === 'todo') {
    const docObj = Object.assign({}, doc.fields, { _id: doc.id });
    store.dispatch(addTodo(docObj));
  }
  if (doc.collection === 'users') {
    store.dispatch(setLoggedUser(doc.fields));
  }
});

asteroid.ddp.on('removed', (removedDoc) => {  
  if (removedDoc.collection === 'todo') {
    store.dispatch(removeTodo(removedDoc.id));
  }
  if (removedDoc.collection === 'users') {
    store.dispatch(unsetLoggedUser());
  }
});

asteroid.ddp.on('changed', (updatedDoc) => {  
  if (updatedDoc.collection === 'todo') {
    store.dispatch(editTodo(updatedDoc.id, updatedDoc.fields.finished));
  }
});

export default asteroid;  

It is important to say that you don't need to have your realtime data synced everywhere. In fact, here we don't need to have auto updates if something appears in the collection. Although we would want to have it in some kind of chat application. For now, in this demo app, when you open two browser windows and you add a new todo item in one window, it will also appear in other window. This is because we have those listeners configured.

We have some action creators configured in for example: app/components/Todo/TodoActions.js file and we call them whenever something changes in the Todo collection.

All other stuff like adding new todo items, removing them or marking as finished is done through methods which are configured in the Meteor backend app. When you open the app/components/Todo/Todo.js file you'll see that we use the edit and remove actions here. callRemoveTodo and callEditTodo methods are just dispatching proper actions creators which are configured in app/components/Todo/TodoAsyncActions.js file:

import asteroid from '../../common/asteroid';  
import { addTodo, getAllTodo, removeTodo, editTodo } from './TodoActions';

export function callAddTodo(message) {  
  return dispatch => asteroid.call('addTodo', message)
      .then(result => dispatch(addTodo({ _id: result, message })));
}

export function callGetAllTodo() {  
  return dispatch => asteroid.call('getTodos')
      .then(result => dispatch(getAllTodo(result)));
}

export function callRemoveTodo(_id) {  
  return dispatch => asteroid.call('removeTodo', _id)
      .then(() => dispatch(removeTodo(_id)));
}

export function callEditTodo(_id, finished) {  
  return dispatch => asteroid.call('editTodo', _id, finished)
      .then(() => dispatch(editTodo(_id, finished)));
}

We need to dispatch async actions which will use Asteroid to call remote methods configured in the Meteor app. Those async actions will then dispatch actions from the actions.js file which will modify the global store and state.

The same is when we want to add a new todo item. When you open the app/components/Home/Home.js file, you'll see that here we use the callAddTodo method which dispatches the proper action creator.

import asteroid from '../../common/asteroid';  
import { addTodo, getAllTodo, removeTodo, editTodo } from './TodoActions';

export function callAddTodo(message) {  
  return dispatch => asteroid.call('addTodo', message)
      .then(result => dispatch(addTodo({ _id: result, message })));
}

Of course you can just use Asteroid and make calls to the remote methods in any place in the React app. Using Redux is just a cleaner and better approach to this problem, but it is very flexible. You can rebuild it as you like.

It is worth mentioning that all data is stored in the Redux store. We just 'map' through it in the app/components/Home.js file.

Ok, what about bundling and hosting?

This is the awesome part. When you are ready with your front-end React app, you just need to run npm run build. The app will be bundled into the public folder. All you need to do is to grab the files and host them somewhere. You'll just need to have standard static hosting. Nothing special. But remember that you still need to host your backend app somewhere. This could be a bare ip address without the domain name, but it should be available. You also need to remember to provide the proper endpoint in the app/common/asteroid.js file.

I haven't tested it yet, but probably you will be able to create many different static front-end apps and connect them to one backend Meteor app which will provide some MongoDB collections and API with methods and publications. This way you can host many static websites on standard hosting and connect them all to one Meteor backend with many different collections. Of course this isn't a solution for big apps, but sometimes it could be helpful. You can create something like your own pseudo BaaS.

Testing

If you want to test your front-end app, we have a ready to use environment. When you go to the tests folder you'll find two example test files. We can also test React components using Mocha, Enzyme and JSDOM. You can write all your tests in that folder and then just run npm test. Mocha runner is configured to test DOM elements using JSDOM and you can even test CSS Modules. This is possible because of babel-plugin-webpack-loaders and separate Webpack configuration for tests. Check our .babelrc file.

Ending toughts

In this article I tried to show you another architecture where you are able to use Webpack, React and all the goodies without giving up the Meteor realtime backend which is really fast and simple to configure. Just one file with a few lines of code! This is awesome.

This approach isn't battle tested, but I think in many cases it could be very good, especially when you want to gain much more control in build process. When you want to have much more control over your data flow, and also when you love Webpack and need more flexibility on the front-end part of your app.

All parts are swappable so you don't have to worry that someday in the future you'll need to rewrite most of your app because something is not so performing the way you want or some new tools appeared and you can't use them in your Meteor stack because of the closed build system environment.

Also these are just my own opinions, and I love Meteor so don't think this article is a rant or something. I just think that there are some things which could work better in Meteor. Despite this, it is awesome that we can use DDP and just connect to the Meteor realtime backend, not only from the JavaScript app but also from ObjectiveC based apps or ReactNative apps. This is really cool!

If you want to check out another example of DDP usage, you can play with the SimpleChat.Support app. It uses another DDP client in the chat box widgets to connect to the main host app.

Also take a look at Vue.js + Meteor project built with this stack.

Anyway, thanks for reading! I hope you've enjoyed it.

If you want, you can leave your comments below or write to me on Twitter.

Comments

comments powered by Disqus