Create a Meteor App Using NPM Modules

Tweet

With version 0.6.0, Meteor introduces NPM support. But full NPM support is only available for meteor packages. Only the core NPM modules used by meteor can be loaded from the meteor app. That means if you need to use an NPM module like redis, github, or winston you are out of luck. But it’s not impossible.

Recently, though, I’ve come up with a smarter way to use NPM with Meteor. It is bundled as a smart package and very easy to use.

For this tutorial, I will guide you to create a Github Issue Search application using github NPM module. I will go through step by step, how you can build it. But I assume you’ve a pretty good understanding of Meteor and Meteorite.

You can see the live application on http://gh-search.meteor.com and code is hosted on githubGithub Issue Search Meteor App

Creating the App

Let’s create our app.

mrt create gh-issue-search
cd gh-issue-search
mrt add bootstrap

We don’t need files created by Meteor automatically, so make sure to delete the following files.

gh-issue-search.css
gh-issue-search.html
gh-issue-search.js

Lets add NPM support

For this tutorial, we’ll be using github NPM module and it will be added as shown below.

Install npm smart package from atmosphere.

mrt add npm

Create packages.json file on the project root and add the following content.

{
  "github": "0.1.10"
}

packages.json is the file where we define NPM modules to be loaded. Make sure to set absolute version numbers when declaring the version. Something like 0.1.x won’t work in Meteor.

Coding Meteor Client

Create a folder called client and add the following HTML and CSS files. We are not too worried about these files since our focus is to work with the NPM module.

file: client/home.html

<head>
  <title>github-search</title>
</head>

<body>
  {{> home}}
</body>

<template name="home">
  <div class='container'>
    <h1>Github Issue Search</h1><br>
    <input type="text" id='user' placeholder="Enter User Name" value="meteor">
    <input type="text" id='repo' placeholder="Enter Repo Name" value="meteor">
    <br>
    <input type='text' id='keyword' placeholder="Enter Search Keyword"><br>
    <button id='search' class='btn btn-info'>Search Now</button>
    <hr/>
    {{> results}}
  </div>
</template>

<template name='results'>
  {{#with error}}
    <div class='alert'>
      {{reason}}
    </div>
  {{/with}}

  {{#each results}}
    <div class='result'>
      <a href='{{html_url}}' target='_blank'>{{title}}</a>
    </div>
  {{/each}}
</template>

file: client/main.css

h1 {
  margin-top: 30px;
}

.result {
  padding: 10px 0px 10px 10px;
  margin: 5px 0px 5px 0px;
  border-left: 5px solid rgb(200, 200, 200);
  font-size: 16px;
}

file: client/helpers.js

Template.results.helpers({
  "results": function() {
    return Session.get('results') || [];
  },

  "error": function() {
    return Session.get('error');
  }
});

Search Results and Errors will be rendered in the templates via Session Variables. All we have to do is set the Session Variables and the UI will be changed accordingly.

Implement the Search

This is how we are going to implement the Search.

  • We are implementing our Search as a Meteor method.
  • Npm github module will be used to do the actual search.
  • Search request details (user, repository, search keyword) from client will be send to the Meteor Method withMeteor.call
  • Meteor method result(or error) will be captured in the Meteor.call and it will be set to the correct session variable.

Listening on Button Click Event

We’ll be listening on the button click event and text field values will be send to the method named search.

file: /client/events.js

Template.home.events({
  "click #search": function() {
    Session.set('error', null);
    Session.set('results', null);

    var user = $('#user').val();
    var repo = $('#repo').val();
    var keyword = $('#keyword').val();

    $('#search').html('Searching...');
    Meteor.call('search', user, repo, keyword, function(err, results) {
      console.log(results);
      $('#search').html('Search Now');
      if(err) {
        Session.set('error', err);
      } else {
        Session.set('results', results.issues);
      }
    });
  }
});

Using the npm module

Now it’s time to implement our method in the server. Let’s create a folder called server on the project root and add our method as shown below.

file: server/methods.js

### CodeBlock: 1
Meteor.methods({
  "search": function(user, repo, keyword) {
    ### CodeBlock: 2
  }
});

Places which are marked as CodeBlock: x will be replaced with the following code.

First let’s load our NPM module. Normally loading NPM module is the job of Npm.require(). But in order to load modules defined in packages.json you need to use Meteor.require() instead.

Let’s load our Github module in CodeBlock: 1

var Github = Meteor.require('github');
var github = new Github({version: "3.0.0"});

Let’s search with github npm module

But wait a minute. NPM modules work asynchronously, but Meteor methods work synchronously. They don’t play well together.

However, the npm smart package introduces another useful method called Meteor.sync which fixes the issue. Let’s see how it works.

This code will be added to CodeBlock: 2.

//this is the search request object (which is accept by our github npm module)
var searchRequest = {
  user: user,
  repo: repo,
  keyword: keyword,
  state: 'open'
};

//execution pause here until done() callback is called.
var repos = Meteor.sync(function(done) {
  github.search.issues(searchRequest, function(err, searchResults) {
    done(err, searchResults);
  });
});

if(repos.error) {
  throw new Meteor.Error(401, repos.error.message);
} else {
  return repos.result;
}

Meteor.sync() will pause the execution of the meteor method until done() callback is fired. So now we can safely do any asynchronous task inside Meteor.sync.

Once we have the results (or error) we can resume the execution by calling done callback.

done() accepts the first parameter as an error object and the second parameter as the result. These values are then sent to the Meteor method as the return value of Meteor.sync()

Return values are encapsulated into an object, as below.

{
  error: {error: "object"},
  result: "resultValue"
}

The other part of the code is self explanatory.

Here is how our final code looks.

var Github = Meteor.require('github');
var github = new Github({version: "3.0.0"});

Meteor.methods({
  "search": function(user, repo, keyword) {
    var searchRequest = {
      user: user,
      repo: repo,
      keyword: keyword,
      state: 'open'
    };

    //execution pause here until done() callback is called.
    var repos = Meteor.sync(function(done) {
      github.search.issues(searchRequest, function(err, searchResults) {
        done(err, searchResults);
      });
    });

    if(repos.error) {
      throw new Meteor.Error(401, repos.error.message);
    } else {
      return repos.result;
    }
  }
});

Showtime

Let’s run our application with mrt and on http://localhost:3000. A hosted version can be found onhttp://gh-search.meteor.com.

If you encounter any errors, try comparing it with the github repo.

NPM modules are not limited to Methods

We can use npm modules anywhere in the Server. Of course, you can use them inside Publications, Permissions along with the Meteor.sync.

And if you don’t need synchronous behavior, just use the NPM modules without Meteor.sync. No-one is going stop you from doing that.

I personally think this is a really good opportunity for Meteor. Technically there are 30,000+ NPM modules waiting to be used with Meteor.

That’s a lot of potential.

Free book: Jump Start HTML5 Basics

Grab a free copy of one our latest ebooks! Packed with hints and tips on HTML5's most powerful new features.

No Reader comments