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 github.
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 with
Meteor.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.
Arunoda Susiripala is a NodeJS Consultant who is well experienced in Server Side Technologies who is recently fell in love with Meteor.