What do you want to learn?
Leverged
jhuang@tampa.cgsinc.com
Skip to main content
Pluralsight uses cookies.Learn more about your privacy
Building a Realtime Web Application with Meteor.js
by Rob Conery
Go beyond a slick demo, and build a usable eCommerce site with Meteor.js
Start CourseBookmarkAdd to Channel
Table of contents
Description
Transcript
Exercise files
Discussion
Recommended
Introduction
Introduction
Welcome to Building a Realtime Web Application with Meteor.JS here at Pluralsight. My name is Rob Conery and I'll be your host for this course. If you need to get a hold of me you can contact me on Twitter or just send an email, you see addresses on screen. In this course we're going to build a web application using Meteor.js and we'll go beyond the slick demos and try to build something real, something useful. My goal with every course here at Pluralsight is that you'll come away learning something new and also have a useful tool that you can use later on. In our case, we're going to build an e-commerce application with Meteor.js 1.0. This isn't your typical Meteor.js demo. No task lists, no leader boards or hacker news clones for us, I want something more complicated. I want to get into the weeds with Meteor and see how well it responds, and you'll do that with me in this course. We'll push the limits of what we're comfortable with, then build a store that I would happily use today, and if you're thinking, is this really a valid use case for Meteor, I could counter with, why not? Web is moving to real-time apps really quickly. Perhaps it's time to rethink what our idea of a website is. I'm getting ahead of myself. I'll address these questions and more in just a few minutes. Let's get back on track. Meteor is a new way of building web applications that embraces real time interactions with your users. It's not quite the single page application framework like Ember or Angular and not quite a full stack web framework like Rails. It's just in between those. If you have a look at the project homepage it tells you right up front all the things that you can expect from Meteor and having built and deployed an application with it I would say that I agree with the assertions here. For the most part, it is a joy to work with, although yes, from time to time it can be a little bit vexing. More on that later on. To get the most out of this course you should have a good working knowledge of Javascript, a familiarity with Meteor and single page applications, and a willingness to try something new. That last one, that one's important. Meteor is very new and very different. You should be open to that. If you haven't used Meteor before you might want to take a minute to catch up on the basics with John Sonmez here at Pluralsight. I'll do some spot reviews as we go along to talk about the basic concepts, but I'm not going to cover everything as well as John does, so if you don't mind a quick ride through the concepts, then continue watching, but if you feel a bit shaky and need some more information you can always pause, watch John's video, and join us again later on. In this course we'll be building a real application that I feel you can put into production today, a simple e-commerce storefront. Along the way we'll do a quick review of Meteor's basic functionality and then do a quick prototype of our site, providing membership capabilities as well. Then we'll get into building and testing out the core. I won't spend too much time with tests because frankly it's boring to watch other people write tests, but I will show you how it works. Then we'll go face first into the complexity of building a real app beyond the demos you normally see. We'll pull in Stripe and Postgres, we'll use Knockout instead of Meteor's built-in rendering engine, and we'll give ourselves hooks for RabittMQ later on. This leaves me with a bit of a challenge. I want this course to center on building something real, yet I also need to be sure that we explore Meteor and not how to build an e-commerce store, so I'll pull back from a few things, like I know I need address validations and message queuing and emailing and so on, because I need to keep us on a straight and narrow path where we explore Meteor and not a bunch of theory about code. I also want to mention here that the graphics that you're going to see during this course, they are all creative commons with reuse or public domain. The ones above in particular deserve special mention if you haven't seen them already. These images were created by SpaceX, which is the space exploration company run by Elon Musk, one of my heroes. They made these graphics for fun and then turned them into public domain, which I think is really neat. Here's the exchange that was had on Twitter. I watched this go down and I just have to bring this up. Elon Musk set SpaceX's Flickr feed to Creative Commons, which is pretty neat, this is where the graphics are stored. Twitter user Pandoomic asked about making them public domain and a few minutes later, boom, it was done. I think that's awesome. Way to go Elon, so all the images you're going to see in this video are either Creative Commons with reuse or public domain. I think that's great.
Getting Your Head Around Meteor.js
Let's take a second and get our heads around Meteor.js and what it does. There are a lot of concepts working together to make Meteor happen and understanding them right away is important. Is Meteor a single page application? Yes and no. While it quite literally is a single page in the browser, you're end users are seeing different URLs and different states of your application. This indeed is much like Ember or Angular. The difference is that those frameworks are intended for single-use applications. Meteor can handle a whole lot more. It can do this because Meteor owns the entire stack. The server is powered by Node and uses MongoDB to persist data. In the middle, what I'll call the translation, uses a shadow dome to assemble the page and inject styles and scripting, which it then delivers to the client. That client is connected to the server using HTTP, as well as DDP, Distributed Data Protocol, that's how it sends data back and forth. The easiest way to think of DDP is that it's rest for web sockets and, as the Meteor documentation says, it goes beyond simple data transport in that it supports PubSub, and allows the client to react to changes in data. Ember and Angular don't do any of these things. What this amounts to is a complete Javascript experience, MongoDB, Node, and Client Code. It's a Javascript funhouse. If you like Javascript you'll love Meteor. If you tolerate it, well you might be in trouble. One of the driving concepts behind Meteor is data everywhere and to accomplish this they've embraced MongoDB completely. You can run, almost, the exact same query on the client as you can on the server, and it's all MongoDB syntax. The Meteor team accomplishes this using a library called miniMongo, which is essentially a clone of the MongoDB API for use in the browser, which brings me to a very interesting concept within Meteor. When you work with data on the client, let's say you update the title on a product, the results will be almost instantaneous and reflected on the current page. The reason for this is that the product data is frontloaded to the client, so when you query for it you're actually querying data that already exists in the browser. When you change that data you change it in the browser and in the background miniMongo synchronizes it up with the server using DDP. This is called latency compensation. It keeps the UI fast, so saves don't make the experience stuttery. When the data is ready on the server it's then published to all the connected clients who then receive the update. Now this might seem a little strange, but we're going to talk about this again in a later module after we talk about publications and subscriptions, and hopefully it'll make more sense then. Seeing data change in the client is scary. It opens up all kinds of security issues in my mind and I'll address all of these in due time. For now know that it's okay. We can lock this thing down easily. The whole idea of PubSub is central to Meteor, something they call being reactive. Changes on the server are pushed live to all clients that are subscribed to that data, which makes for a pretty powerful experience. For our storefront imagine being able to change the price of a product for someone on the fly. You can see they're reading about it, why not drop the price a bit while they shop or chat with them and answer questions. Meteor allows you to rethink the way websites work, offering an interactivity and immediacy that simply didn't exist before. Users will very much like this snappiness of your sight, as well as this usability. You or your boss or client will be inspired when you tell them what's possible with real time interactivity. To answer your question that you might be asking or if you're not, but someone will ask you, do you really need a real-time app here? My answer is why not? Those phones we have in our pockets, those apps move very quickly and respond immediately, they feel natural. People are getting used to this. They're getting used to seeing Twitter and Facebook update itself right underneath them, so why not create your application in the same way?
Cutting to the Chase
I know many of you are here to figure out if Meteor is a workable solution for your next project or maybe your current one. Let's get right down to it, see if I can answer your questions up front right now. I'll get you the actual details later on in this course. When considering a new platform that you're building your business on, you have to answer some basic questions upfront. Can I test this app easily during development? How easy is it to deploy and maintain? Given that meteor is essentially a single page application, what about SEO? Can Meteors scale and is the platform itself mature yet? These are all reasonable questions. Let's talk about each one. I'll start out by answering these tangentially, with would I use Meteor in production, and the answer is yes without a doubt. However, there are different use cases where I might consider something else or maybe using Meteor with another platform. For instance, I might use a straight CMS platform like Jekyll or WordPress for the front of an application and then subdomain the Meteor app for a given purpose, the tekpub, for instance, and I needed a customer application that would allow for purchase, management, and streaming of videos creating bookmarks and leaving comments perhaps. The front end web app could be Jekyll and devoted to driving signups or subscriptions or blogging things about the company. Either way, the answer is yes, I would happily use Meteor in production. In terms of testability I have to say this was one area I was the most worried about, but Meteor has testing built-in. As you can see, it's gorgeous. When you change your code this test runner will run using Mocha, which I'm using here or you can use Jasmine or the robot testing framework. It also collapses in the upper right corner to a red and green light. That's not seamless, however, it can be a little bit slow in the uptake, but I like the testing story. It does have a few little frustrations you have to get used to, but all in all it was a very pleasant experience. I'll talk more about this later on. For deployment and maintenance you have a number of options. The first is using meteor-up from Arunoda Susiripala, I hope I said that right, that's him in the bottom right. I show you his picture because if you work with Meteor you're going to see his name often. He is the TJ Holowaychuk of the Meteor scene writing numerous articles and helpful tools. Anyway, meteor-up will SSH into your server, build it up for you, installing all the software you need, and it will then manage deployments of your application, it's an all in one tool that's really easy to use, sort of. It's not without its frustrations, however, and I'll discuss those later on, and they're all workable, as long as you know what you're doing. There are also hosting companies out there like Modulus that'll handle things for you in a turnkey style, so yes, deployment and maintenance is very straightforward. If you have a web application that needs SEO you'll want to be sure to install the spiderable package. Google is getting pretty savvy these days at indexing Javascript heavy sites. In fact, just over a year ago they started rendering the sites with their spider, rather than simply crawling the HTML. This doesn't really help with Meteor, so for that you'll want the spiderable package, which will handle crawlers when they come around, and give them HTML that they can digest rather than just a bunch of Javascript. As an aside, you'll notice quite often that answers to many problems with Meteor center around the available packages, and this is a good time to mention that Atmosphere, which is the Meteor package site, has almost 6000 packages available that solve all kinds of things, and offer some amazing functionality to boot. As you'll see, these packages can format your site, run tests, help with SEO, and even format a groovy alert system for your users. This right here is one of the main strengths of meteor, its community, and the available packages. Alright, community is neat and all, but how well does Meteor scale? How can it handle a ton of traffic with all these concurrent connections, and the short answer is that, yes, it scales fine. Like any framework you need to know the pain point and the bottlenecks, and what you can do to minimize potential problems. I'll talk about these later on, but upfront you'll need to know how to manage your subscriptions and publications with care, maybe use a CDN to serve certain assets, and be ready to partition Mongo if need be. You can also do things in a more distributed way using a message queue. There's always an answer, so if you need to amp up Meteor you can. Finally, how mature is Meteor? Straight answer here. It's getting there. While it did hit version 1.0, there are still a few bumps in my mind. For instance, MongoDB is the only supported database. There are others in the works, like RethinkDB, and there's even a Postgres package. The lack of choice here out of the box is a bit of a bummer. Blaze, the rendering engine, was just added to Meteor, and it still feels a little bit young. If you want to automate the UI or have some fun animations you'll need to figure out how to work with a third-party tool like Knockout or React and I'll use Knockout later on and some people look at this and they say, well that's actually a good thing, use the libraries for what they're good for, and I agree with that. The good news is that these things are all easily managed. You can use any NPM module with Meteor, so that means you can talk to any database you want that has a node driver, and we'll do that too later on. Now that I've answered a few questions up front, let's write some code, and you can see Meteor in action.
Prototyping
Generating the Site
Let's get off the ground by prototyping our application. In this module we'll generate the site and get the initial formatting down. Then we'll add in some routing. Next we'll add a Homepage and we'll begin working with data. Then we'll create a product page and, finally, deploy to Meteor.com for review. Let's get started installing Meteor.js. I'm here at Meteor.com and here, once again, you can read over the documentation and you can take a look at some of the marketing stuff down below. If you click on Install Meteor you can see that is a simple curl command and it's going to download an installation script and it takes about 10 or so seconds to get the whole Meteor platform installed. I already have it installed, so if I type in Meteor here at the command line you can see it says, you're not in a Meteor project directory, you have to give it a command, and if you want to know what those commands are you can type in meteor help using a --help. That's always a good thing to do because then you can take a look at all the commands at your disposal and in here you can see we have plenty of them and we'll go over them, some of them, later on. The big one that I want to play with now is create because that is going to create our site for us using a generator and it's going to create a directory and drop in some initial files, so if I change directories into rocket-shop we can see our brand new site, and all we have to do is run Meteor. It'll sense that we're in a Meteor directory and it'll start the server, startup MongoDB, and we'll be good to go, so let's take a look at localhost:3000. Yay! Welcome to Meteor! Not a terribly motivating or exciting experience, but they do show you this Click Me button, and if you click it you can see a counter go up and I suppose that's exciting, but let's just kind of scoot right on past that. What I want to do next is I want to install the Bootstrap starter template, as well as I'm going to be using Bootsnipp for a lot of the HTML templates that I'm going to be using later on, so I want to plum those things in straightaway, so let's take a look at what's in our directory right now. We have rocket-shop.css, rocket-shop.html, rocket-shop.js. If you've played with Meteor before, which I hope you have, then you know that these things are not anything that we want. What we do want to do right now is we want to actually structure this in a better way, more than just having a demo. I'm going to create a set of directories and these all have meaning to a Meteor application, server, client, public, and lib, and in addition I'm also going to add a settings.json file. We'll talk more about those later on, but I want to have two of them, settings.development.json and settings.json. These will hold various application settings that we'll need later on. For now let's open up web storm and I'll do that from the command line, and take a look at what we have as far as a basic skeleton of our site. What I want to show you here is a traditional kind of standard directory for working with Meteor, the other .meteor directory that holds all the magic. We're not going to worry about what's in there. We have client, lib, public, and server. In addition, we have settings for development and settings for production and just the regular old settings.json page. Let's take a closer look at the other directories, and we'll start with the lib directory and this is important. Lib can be accessed from everywhere and if you've used Meteor before you know that you have code that you write that could be accessed in the client, the browser, as well as the server. That code goes in lib. If you only want code on the server, then you put it in the server directory. If you only want code on the client, well, you put it in the client directory. The public directory is for images and other assets, not necessarily style sheets, you put style sheets separately, they get collected and compiled by Meteor. I should say if you don't know what I'm talking about right now, well, you might want to go watch John Sonmez's video. He talks about all of these things. However, if you're the kind of person that can easily pick things up as you go along, and you don't mind a little frustration, well then hang tight, we'll talk about all this stuff, not necessarily now though, so fair warning. What code you put where is quite important and not only that, the name of the file is important as well, if you're depending on load order, in other words, what code gets loaded when. Hopefully you're not worried about that because if you are, then Meteor can cause problems for you. We'll see that later on and we'll also talk about load order a little bit more later on. Just know for now that anything in the lib directory will be loaded first, then comes server, then comes client. Files inside those directories then get loaded in alphabetical order and that includes their full path, so if you have subdirectories in there, well those subdirectories are going to be evaluated when loading everything in alphabetical order, so the bottom line, outside of the lib loading first, and then server, and then client, don't depend on load order. That actually can come back to bit you. We're going to tango with it later on when we try and do some namespacing gymnastics, but we'll get there. For now let's fill out our client directory here. What I want to do is I want to make sure I have a specific directory for templates because templates are going to be the things that are rendered out to our users. The next thing I want to do is create a directory for style sheets. As I mentioned, Meteor will take all the style sheets for the client and it will compress them and concatenate them down for you, which is incredibly handy. In addition, we'll be using some third party scripts like Knockout and Accounting and Moment.js, so I'm going to put that in a scripts directory here in client. Then I want to have a special app directory. In this directory I want to put things like my layout, so let's add that right now, and I'll just create a file and I'll call it layout.html and I'll drop it straight in and it'll be empty for a second, but let's jump back over to Chrome, and I'm going to copy and paste the Bootstrap starter template here, I'm just going to copy it straight from the page and drop it in. There we go. I have a few links here that I have to change and I'll do that in a second, but right now what I want to do is I want to show you something interesting about Meteor. Let's startup our server again, and when we do notice that we get an error. It's saying in your app layout.html file you can't set the doc type here, Meteor sets it for you. Now if you are familiar with Meteor, then you know all about this and the shadow DOM, but if you are not familiar check this out. Meteor has a shadow DOM that it creates kind of in the background, in memory if you will, and it takes all your HTML files and injects them into that shadow DOM and then renders them out to the page. Refreshing, there's our starter template and it handled all the doc type for us and everything, that's kind of nice, and if we take a look at the page source here you can see what a Meteor app really is. There is our doc type tag. Right here we have a big fat dump from the Meteor runtime and it's the runtime configuration. Reminds me a lot of asp.net. That's pretty funny, the post back, and then down below that a bunch of script tags with all the individual packages that make Meteor run, including some of our client code. Finally, down below here, well that's our Bootstrap styly stuff and it's all broken, doesn't look very good. Our next task, let's put some HTML in this screen and make things look a little bit better.
Bootstrap
Adding and formatting Bootstrap by hand is so two years ago, we're working with Meteor. There's got to be a package for that and indeed there is. Here we are at twbs, Twitter Bootstrap, /bootstrap up here at GitHub.com, and if we want Bootstrap in our site we can just add that package, meteor add twbs:bootstrap. That's going to go out and grab the Twitter Bootstrap package and drop it directly into out site, so if we come back over here and refresh, hey look at that, boom, we have Twitter Bootstrap already put in for us. Taking a look at our source, however, hmmm, well what's all this? That's all the stuff that was there before that I had put in in my layout.html file. I don't really need that anymore, so let's jump back over here and we'll take out all this scripts stuff and then I'll come up here to the head and take out all of that as well, so if I save that and refresh, everything pretty much looks the same, except eww, I lost that title. Well this is a really neat thing. I can put that title right back by just dropping it in the middle of a head tag and there it is, The Rocket Shop. That's pretty neat. You can have multiple head tags and multiple HTML files and what Meteor will do is we'll assemble all of those things and inject them for you, so you can see there is our title tag dropped right at the very bottom below the big old mess of script tags that meteor gives us, but why is this body tag empty? This is a little bit off-putting. If it's the first time you view source in a Meteor app and you say to yourself, wait a minute, I know that I just pasted all of that HTML from Twitter Bootstrap, I just pasted it all right here. We saw all the metatags and the head tag stuff, what's the deal? Well that's the deal with meteor. What it does, as I mentioned, is it works with a shadow DOM in the background. It'll squeeze out anything inside the tags that you have in your markup in your templates. Anything in a head tag, like our title here, well that will get dropped down when the page loads, and that is all sensible. However, with the body that gets dropped back into Javascript and injected after the site renders, so there it is. The way we can see this is by inspecting the element and there is our div with our container, starter template, and so on that was squeezed out, shoved through Javascript, and dropped on the page. That's Meteor, lots of magic. I think it's pretty fascinating that it can do this for you fairly seamlessly. Alright, well what we really want is we want some better formatting, so let's go and look at the page source and we have to drop in a little more CSS to make sure things align properly, so I'm going to copy this out of the starter example here and I'm going to create the new sell sheet and I'll just call this app.css and inside of here I will paste in this stuff and let's take out the starter template stuff, I don't really need that. Come back over here and look at that, our page has already adjusted and changed itself. That's the next fun thing about working with Meteor is the live update stuff. If I change the CSS here we get to watch the site change there. That's great. If you change a code file or any HTML templates the page will refresh itself. I think that is so neat. That padding looks pretty good to me, we'll keep that just the way it is. Hmm, my organization seems to be a bit off. Let's move that app directory down into the template directory because, after all, layout is a template. Going back over here to our site and notice it didn't matter. It doesn't care about where your files are located, as long as you have HTML files in there Meteor will do its magic. In addition, let's have a partials directory in here, and what I think I'm going to do is I want to put the navbar stuff inside of a partial. Working with partials and Meteor is pretty easy and it's all basically feeling like mustache. If you're familiar with ember and mustache templates it works almost the same way. Inside of here I'm going to create a nav.html file and I have, there it is, our navbar, I'll just paste that right in. That looks pretty good and then what I need to do now is go over to our layout and just ask for that partial, and again, if you're familiar with mustache or handlebars it is this syntax right here to ask for a partial, and hmm, something doesn't look right. Let's check our console. Unexpected token Y. You're going to see this a lot and I don't know why it continues to say that. Instead of saying bad formatting and telling us in the console it tells us if we refresh the page we have bad formatting, and this happens all the time. Why is that? Well we've asked for our nav-partial and if you come from the Rails world, well then, you know that it looks in a partials directory for a thing with an underscore called nav right? Well Meteor and Blaze work differently. You have to have a template with a certain name. It does not care about your file name or location, it needs to see a template with that name, so it will be injected, and there we are. Template with the name of nav, boom, and we now have things a little bit more concise and put together in a nicely organized way using partials. Again, make sure that it is named appropriately. Again, you don't care about the name of the file.
Routing
Our little application's going to have multiple pages, so we're going to need routing and, oddly, routing is not included out of the box with Meteor, which I guess I understand, just add packages as you need, so for this we will be using iron router, which is kind of the go to router that a lot of people use, although there are alternatives out there. For instance, if we go over to atmosphere at atmospherejs.com we can go and explore some Meteor packages here, and if I put in routing, or should I say routing for my friends across the ponds, you can see iron router is number one, but there's also other ones in here. We have respondly, there's mystor. There's a couple of others, but iron router is kind of what everyone has agreed on. Some people don't care for it because it does everything, as you're about to see, and it doesn't just do simple routing, but for what we need to do it's pretty simple stuff, so we'll just go and do a meteor add iron:router and then it drops it right in for us, as you can see, we have an iron:controller cord, dynamic-template, dah, dah, dah, dah, dah. There's a lot of functionality to this thing, but I'll tell you right now, I'm just going to use the basic functionality of it because I find that's all I really need. You can get into having route controllers if you want, but I find it just muddies the water, so for us let's add some simple routing, and the first thing I'll do is I'll create a routing file. You can call this router.js if you like, routes.js, but for me it's router.js because we are going to configure it and set things up, so this will be the router settings, makes sense to call it router.js, and if you've never worked with Meteor before, welcome to global variables, and well, we have them on the client if you use something like Ember or Knockout or even jQuery, there are global variables that you bounce against, and fortunately with the packages in Meteor you get things like Router and well there's other problems that you run into. We'll talk about that more later on. Okay, so in here I need to configure the router. I need to tell it which file holds my layout. In addition, I get some extra sweetness here where I can say I have a loading template, in other words, when the page is loading and the Javascript is coming down, what page do I want to show to the user, and that'll be loading. Finally, I have a notFound template, so if there's a problem and the user goes to a route that doesn't exist, well we're going to show them a nice 404 page. I'll get to that in a second, but straightaway I have something I need to take care of. This is my layout, but how is the router going to know it because I already said that it needs to use layout as the layout template, but this is just straight up HTML. Well if I go over to our page here you can see that the iron router pops out a little message for us, Couldn't find a template called 'layout' or 'layout', so what I need to do is I need to wrap this in a template tag. The router now is taking control of all rendering and this is kind of one of the things that I was going to mention to you that beyond being a router it is a global rendering package too. Some people don't like that, other people do. Whatever, I'm just playing along, and there goes our error, so it's showing up just fine. Next, if we view source here you can see we also have another problem, duplicated head and body tags. Why is that? Well the short answer is that our layout.html page has just become a template. That means it's being injected into the Twitter Bootstrap stuff, whereas before the shadow DOM was squeezing out the head and the body stuff and injecting it in, well that's not happening anymore because now, as I mentioned, layout has become a template. That means we need to take this stuff out of here. No more body tag, no more head tag, but we can keep our little div in here if we like, and we can keep our nav as well. Alright, let's go back over here and you can see it's already refreshed and good. Only one head tag, but we have a problem, we have lost our page title, it is no longer in the head. How do we deal with this? Well the same way that we dealt with it before. We have a number of places that we can put this, but what I'm going to do is I'm going to create a main.html file, and inside of here I will just drop in a head tag and inside of here a title and I'll just put The Rocket Shop. Now if I want to change this later on I can just use Javascript to do it. I'm not going to deal with that now, but I can just set window.title and that'll be fine. Anyway, you can see we have Rocket Shop, it's back, that's good, and scrolling on down through the mess of script tags, yup, there it is injected right at the very bottom, that's where we were hoping it would be. Good.
Initial Route Templates
Now that our layout is complete I need to add three more pages here or templates. I need to add a loading, notFound, and also a home page, so let's take care of that now. The first one I'll add here is the notFound template and I'll call this 404.html, which is pretty standard for notFound, and you'll want to make sure that you pay attention to this kind of thing because it's pretty easy, at least in the initial goings here, for URLs not to match up or pages not to be loaded, so you want to make sure you gracefully handle that. It'll make you look good, so let's go over to Bootsnipp.com and I'll do a search on 404 and in here I find 2 templates and the first one looks pretty good. You can make a great impression on your managers, bosses, clients, customers, whatnot, if you handle page notFound very gracefully and even with a sense of humor. For now I'm going to put in something that is not just a bland, whoops, something that looks a little bit better, so I'll grab the CSS from here, and I will also grab the template, as you can see, and then in app.css I'll drop in the specifics for this 404 page. Later on I might want to add a product listing, I might want to say, whoops, sorry, looks like a URL's broken, something that makes your users feel like it wasn't their fault, that's very important. Next, I need to add a loading template. This happens a lot with Meteor and it's probably one of the big templates that people are going to see all the time because, as I mentioned, the Javascript and JSON and all that stuff is going to be coming down from the server on load and what we're going to want to do is we're going to want to show kind of an interactive interesting page. Now I could do something from Bootsnipp, but you know you generally get a variation on the spinner, and that's okay, but what I'd rather do is maybe look for something Meteor specific, so let's head over to atmospherejs.com, and if I search on loading there are a few loading packages in here and this one here, pcel:loading, looks pretty good. It's a pretty loading screen for the Meteor app and well, there's a lot of things you can do. Specifically you'd need to have a loading template and it will fire for you, it looks like with or without routing, which is kind of interesting. You just have to have a template called loading. Anyway, if we take a look at the template here in the demo, this is up at meteor.com, love that, looks pretty good to me, and right. Alright, so we can use this one, so let's head over to our console, and I'll just add this package quickly. Now that it's loaded up I just need to augment this template. I already have a template with the name of loading and it looks like all I really need to do is to add some Javascript on the backend and this Javascript is going to fire the rendered event, as well as the destroyed event. I haven't talked about template events just yet, but I will in a coming module. For now I'll just create a loading.js file, which you can think of as a code bind page if you will, and I'll paste in the code that was given to us from the atmospherejs page. Now obviously there's a bit of customization that needs to go on here. Specifically, I need to put my logo in there, and also down below I might want to change that message. Instead of loading-message maybe to something like just a second. I'll tweak all this in our last module when loading becomes important when we start doing publications and subscriptions. Final thing I need to do is to take the CSS that the atmospherejs page recommends and I'll just paste that right in here and, as I mentioned, I'll need to format this and make it look really nice and ready for production, and I'm going to do that in the very last module. Now that we've handled the 404 and the loading template, let's make a more compelling homepage. What do you say? The first thing I'll do is let's take out the starter template stuff here, what do you say, and instead of having this I will just put a yield. If you're a Royals fan or have used templating with a framework like asp.net mvc, well you know that the keyword yield means inject a template here based on whatever route I am on currently, so if I refresh this, hey, look at that, that is our 404 page, but why is that showing up? Well the simple answer is that I don't have an index template, I don't have anything configured so far for the route of our application, and for that I need to go back to our router and I need to define something for the route URL of our application, so we do that by saying Router.route, and then just forward slash, that means the homepage. I'll give it a name and for this I want the name to match up to the subdirectories that are in our templates directory, so I'll call this homeIndex with the idea that I'll have a home directory with an index.html template, and do note that having underscores or dashes in a template name is discouraged with Meteor, in fact the standard is to have a camel case like you see. Right here I am being told by the router that it can't find the homeIndex template, and that is true, so I will create a directory called home, and in here I will create index.html and I'll give the template name and this is the important part, homeIndex camel case. Now this has to match. It doesn't matter again, the file name, what matters is the template and the name you give it, so I will put in something here like Welcome to the Rocket Shop. That's much better than Bootstrap starter template and there we are. There's a nice welcome message. I have one last thing that I think that is really worth mentioning. What if you want to pass some data down to the template? How do you do that with Blaze? Here I am just going to output a message and if I refresh notice I don't get an error, that's interesting to me, and I think quite nice. It just is an empty page. It doesn't know what the message is, but if I go back into my router, and I specify data and the data is going to be a function and I just return a value and here I can just specify that the message is, same thing pretty much, why not, Welcome to the Rocket Shop, and then I save, take a look, oop, by the time I save it boom, it's already there on the page, so that is how you pass data from the router down to the template, and we're going to be doing a lot of this later on.
More Routes
Well our site is probably going to have more than one page and even though this is a prototype it's important to show navigation and well basically different pages other than the homepage, so let's do that now. I will create some additional routes here like about and I will put them all in the home directory, so that means I need to name them homeAbout, and the other one I'll create is one for contacts because, well, we're going to have a contact page as well, and I'll name this homeContact, again, camel-cased as you've seen before. Following the structure that I have I could just put about and contact right underneath the home directory. I like this kind of organization, it reminds me a lot of Rails, and it's something that I'm very used to. In here I have to make sure the name is homeAbout or else the router won't be able to find it and it won't know what's going on and I'll add an additional file here, contact.html, and same thing, I'll rename this to be homeContact. For now let's just stub out the title of the page and I'll put in some first level headers here and I'll just put in Contact here and then heading over to eth about page and I will put in another first level header and I'll just put About. All right, well let's go and check our work. If we flip back over to the browser and I type in the URL, let's do /about, there it is, the About page, and then I can do the same thing with Contact, just to make sure it'll show up with the routes, and there's Contact. Excellent. That's nice, but I'd rather have links up here, so users don't have to guess and type in the URL. Let's flip back over into our templates here and what I'll do is I will go over to our nav partial and down below you can see there is the about and the contact links. What I can do to navigate is I can use this navigation helper, pathFor, that's from iron router, and I can just say pathFor 'about', there we go, and I can put 'contact' down below here, and then up here I'll put index, but let me ask you, does this look right to you? Does it make sense pathfor 'index', 'about', and 'contact'? Hmm. How would the router even know what I'm talking about if this is a router helper? Let's go back over to the router and check things out. Here I have routes named homeAbout and homeContacts and homeIndex, hmm, but the URL goes to about and contact, yeah it doesn't make sense, and indeed, that should be the name of the route not the URL that I want to go to. A very very simple error to make and one that's all too common, so here we go, homeIndex, homeAbout, homeContact, that's what I want to see, and clicking on these guys, there you go. Notice also that I don't have a pound sign in the URL. This is because Meteor leans on push state, now with the browser's history API. that's pretty neat. It'll gracefully degrade if it sees older browsers, but since I have a newer browser it's just making it look as if it's a normal URL. The last thing I need to do here is to remove project name from the navbar. Let's put in The Rocket Shop and then also add a Home path and there we go, this is looking pretty good.
Favicons
Well this might seem like a lot of fluff to a lot a lot of web developers out there, but having a Favicon is huge. I went over to the Favicon and App Icon Generator and loaded up our logo and it gave me a bunch of Favicons, as well as some HTML to use, and I want to put that in right now. I think this is actually a great bit of embellishment, if you will, that is going to make your stakeholders extremely happy because it's going to make the app look real, so let's add an images directory to our public directory and in here I'll load up all of the images that are in the downloads for this course. We have icons, products, splash, vendors, and some credit card stuff down below. Specifically what I'm interested in though is the manifest and icon stuff that the icon generator gave to us. Alright, well how to I actually put these in? Well I've created a favicons.html page in our template and basically I copied and pasted all of the HTML that was given to us by the generator and I reset the links, as you can see here and I just put them inside of a head tag. That's right, another head tag. This is going to be injected right underneath our title. I think this is pretty cool. This is one of the neat things about working with a shadow DOM in Meteor. Alright, well let's head back over to our page and make sure that this works, and if I refresh boom, there's our little rocket. That's kind of neat. If we take a look at the page source and scroll down it is kind of as you would expect there is the favicon stuff in terms of the links and then underneath that you can see the title.
Homepage
What we have on our homepage right now is Welcome to the Rocket Shop and we kind of have to offer a little bit more that users that come along because, well, a greeting is nice, but we should probably have more information. The first thing I'm going to do is to drop in some ready HTML. You can grab this from the downloads for this course or if you don't have access to the downloads for this course you can go to the GitHub URL that you see on the bottom of your screen. The HTML for the homepage here is fairly traditional. We have a big old splash at the top and then some features products down at the bottom and to see this I just can't resist, I've got to drag this thing over and I haven't saved it yet, but I just want to show you this one more time because I love it. I'll save it and boom, I love how Meteor just refreshes and shows the latest changes, that's pretty neat, but if you look at the layout here it's kind of stinky. What I'm going to do is I'm going to add some styling specifically for the homepage and I'm going to do this under stylesheets, I'm going to call it home.css. Remember that Meteor takes all of the CSS files that it finds inside the client directory and it will smash smash it together, concatenate, and drop it on the page for you and that still doesn't look so good does it? We clearly have a problem and I think I might know what it is. Let's take a look. I'll stretch this out a little bit, yeah, you can see we have some serious problems. This is supposed to stretch all the way to the end and I'm guessing we have a problem with our layout and our template, so one thing we can do is we can just go to Inspect Element and take a look at the way things look here. There's our cover, oop, we have two containers. We have an outer container and an inner container and this is a big deal when working with a framework like Bootstrap or Foundation, you really need to understand the wrappers and the page wrappers and where they are, especially when you're parsing things like I am, and here I put a container for everything outside of our yield. I don't want to do that. I want to make sure every page has the freedom to format itself and there you go, I remove it, and look at that, it refreshes itself instantly, I love that, and it looks great. Well, except for the little product boxes down below here, I'm going to need to add some styling for that, and I haven't added it just yet, so let's add another CSS page, and I'll call this one product-tile.css. I really like adding the targeted CSS into individual files here that then get crunched down, minified, concatenated, and so on later on and when I save that, boom, it is applied, and that looks pretty good. All we're missing now are the images that go along with these tiles, I'll add that next.
Test Data
We're still prototyping, so I don't want to worry about databases just yet, but I do need to work with some test data, so let's add that now. The first thing I'll do is I'll add a directory called collections in our lib directory. I'm adding it here because I want to have access to it from both the client and the server and if you recall anything that you add in the lib directory is accessible from both, so I'll add just a simple bit of Javascript or JSON if you will, and this is going to be a simple products array and I'll drop that into the collections directory. In addition, I'll add some users as well in users.js in the same exact way. Alright, well this is just markup stuff, this is just static HTML. What I'd like to do is I'd like to replace it with the Products data that I just added and I can do that. What I'm going to do here though is help myself out in the future, so I'm going to create a specific method called Products.featured. In here I'll just stub out some featuredSkus and that'll just be some products I chose at random and then I'll use underscore and I'll filter the products based on those featured skus. By the way, I can use underscore because it's packaged as part of Meteor, you have access to it immediately, which I think is neat. Testing from the client and the console, I have all products there, and products.featured brings back the three products that I expect, and to do that I am going to add a brand new Javascript file here and I'm going to call it index.js and this is going to be in the home directory and it's going to sit right next to you, index.html. Now again, it is not important what you name the files or where you put them. What is important is in the code you type template.homeIndex, where homeIndex is the name of the template, and then helpers. In this case I'm going to create a method called featured and that is going to return Products.featured. I could have called this anything, but I do need to have this exact code in the background, and if it's helpful for you, you can think of this as code-behind if you want to. It's funny because as an esp.net developer years and years ago I looked at this and said, oh yeah, code-behind, makes perfect sense. I should also mention that we have access to Products.featured because we put products in the collections directory in the lib folder, and just to say this again, you have access to this on both the client and the server. That's really important to remember, so I can query on the client, and I can query also on the server. Alright, let's use a little templating magic here, and I'll use each and I'll go over featured. Again, featured is the method that is in our helper, so we'll have access to it here from our template. I'll take out these other little placeholder things and I'll end our each loop right here at the bottom. I'm going to go a little bit slowly here. I just want to make sure that that loop is looping over as I would expect and let's go back and refresh out page and look down, yup, good. Product 1 is repeated 3 times. That is what I would expect because I have three featured products, good, so now let's go back inside here. I can now step into the templates and replace all of the information with that of the product that's in the loop, including the name, price, image, and so on. Refreshing and look at that, we have product data on our homepage and I think that looks pretty good. Honeymoon on Mars, Johnny Liftoff Rocket Suit, and our One-way Re-entry to Earth, but eww, price does not look so good. Let's fix that next.
Accounting_js
In every Javascript application I ever work on, whether it's server or client, I'm always sure to include some go to libraries like underscore, Moment.js, and accounting.js as you see here. This is going to help me with that pricing issue that you saw in the last clip and the price was basically in pennies and it looked horrible, so that'll format it nicely for me. Moment.js, if you haven't heard of it, really helps you work with time in a very nice way because Javascript and dates, that's just no fun. Finally, I'm going to be adding showdown because I like working with markdown. I use showdown all the time, and it's a great markdown converter and you wouldn't believe how often, using markdown in your application can come in handy. I have all of these libraries here in the downloads for this course, including knockout and stripe_checkout, so let's grab all of them right now, and I'll drop them into the scripts directory. By dropping them into there when Meteor starts up it's going to actually concatenate and minify those things and make them available to us on the client from everywhere. If I need them on the server I could drop them in my lib directory. The next thing I need to do is I need to register some helpers, so I can use accounting and moment and whatnot on the client. To do this I need to use UI.registerHelper and I'll drop this into a main.js file. You can call this whatever you like. You can call it helpers.js, whatever. For me I'm just going to call it main.js because why not? Anyway, in here I'm going to register helper and I'm going to call it money and then I'm going to return something, but what? Heading back over to accounting.js well you can see it's just formatMoney. That's all I need to do, return accounting.formatMoney and I can access accounting because again, accounting is in our scripts directory and it will be present on the client for me to use. Finally, I'll just pass along the amount and well we're all good. How do I actually use this brand new helper that I just created? Well as you've been seeing Blaze, the templating engine from Meteor, is a lot like Handlebar. In fact, it is almost exactly like Handlebars, and so using helpers, well you just kind of tack it right in here in the beginning, and that is how you use a helper and you just specify I want to see money, and then to pass something into it, that's the second argument. Down below here, there it is. We have nicely formatted money for our price. That problem is solved pretty easily. While we're here let's save ourselves some work in the future and try and make our template pages as clean as we can. For this I'm going to create a brand new file in partials and I'm going to call it product_tile.html. In here I'll create a brand new template for our product_tile and I'll give it the name productsTile and this is hopefully causing you to see naming conventions that work with Meteor. The file is product_tile, but the name of the tile itself is camel case. That's a really good habit to get into and Meteor's going to help you out with that because the templating engine does not like to see dashes and other characters, so just try and make it all alphanumeric and try and make it camel case as you see here, that is just a convention with Meteor. I could literally just copy and paste the code without modification. To use it I just use the handlebar syntax, productTile, and then pass along this. It's important to note that when you're inside a loop in handlebars or blaze for that matter, this is scoped to the item in the loop, so I can pass that into the template, and as you can see, it works pretty well.
Product Page
We have our groovy little product tiles here, but we don't' really have a product page that we can view the details with, so let's add that now, and I'll go at a little bit of a faster pace here because this is essentially review. The first thing I'm going to do is to add a route and it's going to be products and this time I'm going to specify a parameter using :sku, this is traditional routing kind of syntax if you will. I'm also going to pass in some data and data can be an object or a function. In this case, I need it to be a function, and that function is going to return, now in this case I'm going to have to loop over products and I'm going to need to find the product with a given sku. Find is an underscore function and it'll return the very first item in an array that matches a given condition, so for this I can pull the sku out of the parameters using this.params.sku. Now this works, but I'd rather help myself in the future because I know I'm going to be working with Mongo because that's what backs Meteor, so I'm going to create a method called Products.findOne, and well Products.findOne is going to do the exact same thing for now. Later on I'll take this out when I plug in Mongo. This is going to take some arguments and inside of here I'll just ask for args.sku. Perfect. That means I can go back into my route here, again, helping myself in the future, and I can ask for Products.findOne where sku is equal to this.params.sku. Okay, well next I need to make sure that I have an actual template. Right now I've specified the name product, but I'm going to change that in just a bit, so inside of here I'm going to have products and then show.html. I'm following a Rails convention here where if I want to see an individual model or item, then I'm going to use show, and so inside of show I will dump in productsShow. Again, naming your template is very very important because you're going to want to be able to match a URL against a directory structure, so it makes sense to you later on in the future or with people that come and try and pickup what's going on. Okay, inside the products page here I'm going to add some ready HTML that you can, once again, find in the downloads for this course or you can go and grab the code up at GitHub using the URL I showed you in the few clips ago. Now that we have a router and a template let's add a link. Over here in my productsTile template I can now do a path for productsShow, but the route specifies a parameter, and I haven't specified one here, why not? Well the short answer is that pathFor looks at this to see if it has any property that matches a parameter on the route that you're trying to go for. If it does, well it'll construct it for you using this. Notice, if I hover over more details it does exactly that, it finds a sku on the product, and is able to create the URL for me. Very handy. Okay, so we're heading over to the page, it works, sort of, looks like the formatting's a little bit off, so let's go and fix that. Heading over to my productShow template and yeah, the image isn't set, so let's set that now. We'll do images/products and then image and I can set all this directly because the data is being handed to me directly from the route. I'm not using a helper this time. Well, oh, it looks okay, but it looks like I have some formatting problems. Let's readjust this a little bit. Wrong way Rob, let's give this seven columns by five columns, there we go, that looks much better, and the price looks good too. I'm using the money helper. Man that makes me happy being able to use accounting just like that so easily, but we have another problem. Looks like I am expecting some markdown on this page and hmm, it's not being converted. Let's do that now. I could create a brand new helper and I'm going to just call it markdown and this is going to use showdown and for this I have to use two lines of code, darn, and this is going to create a brand new showdown converter and it's going to return makeHtml, which is going to take the markdown and turn it into HTML for me, and then I can plug that in using markdown just right here in line. Refreshing in that almost worked. It's encoding the HTML for us, which is nice of it. What we need to do is to make this unsafe by using three braces and when I do that look at that, the markdown is working on our product page nicely.
Deploy to Meteor
I'm proud of our little site. How about you? We've done a lot in a very short amount of time. Let's show the world. If we type in meteor--help there's a number of commands in here and there's two to note. The first is login and as it turns out you can have a meteor.com account, which I do, you can see my email address here. It takes a second to sign up, no money is required, you can actually balance authentication off of this thing too, so yeah, go and get yourself a meteor.com account because one of the cool things that you can do is actually deploy your site to meteor.com. Now this isn't for production use, this is more for, hey world, look at my demo, look at the package I made, client look at what I've made for you. This is neat stuff. It's public, you can't lock it down, you should know that, but if you have work you want to share, like a demo, this is great, so if we want to deploy our site up to show our team or other people we can just say meteor deploy and we'll give it a name. In this case, it'll be rocket-shop. Now notice the deployment process for Meteor, this is interesting. It's sort of built-in. It minifies and builds your site ready for production. It builds this big old slug if you will and drops it down on disk and then it'll send it off. We'll see this more later on when we use meteor up, but right now we have our site up and ready at rocket-shop.meteor.com, and we can go there and take a look at what we have just built. Heading over here to the browser, dropping it in. I should mention this does take a while to actually get up and running, a while meaning like a minute or two, not like an hour, but as you notice, I'm missing some graphics, but as I said, it does take a second to go live. If I refresh this thing right now, then boom, there we go, we have our site up. We can then send an email to our client and say, hey look, here's the prototype. Now again, this is not private, so if it's a sensitive site, by all means, you're going to want to have your own server and we will discuss that in module seven. In this module we kicked up a prototype, so we could get off the ground quickly flexing a lot of Meteor's goodness. Then we implemented routing, layouts, and partials. After that we added a home page and stubbed out some test data. Finally, we put everything up for review at meteor.com.
Authentication
Authentication Setup
In this module, we're going to plug in authentication. Specifically we're going to run through a basic setup and installation of all the authentication parts. Then we'll configure it to meet our needs. Next, we'll add external services like GitHub and Google. Finally, we'll augment our membership system by adding role-based authorization. Our application needs membership, and hopefully by now you're starting to see that if you need something, it's always good to go check out atmosphere.js and see what they have up there. So plugging in membership didn't really do much, but if I look at accounts, we'll take a look at what pops up, yeah, there is a ton of account stuff, and in fact, it is kind of already built into Meteor, and it's really, really thorough, I'll just tell you that right now. So we have accounts-password functionality, we have accounts-ui, we have stuff specific to Twitter Bootstrap as you see here, it just plugs right into it. Installation is fairly trivial, do again, meteor add and then ian:accounts-ui-bootstrap-3, and then using it. If we take a look down below, all we've got to do is drop in this login button's partial, and as you can see, it goes right up there in your navbar; you just need to add another unordered list. We can do that. So, let's start out by adding the package ian:accounts-ui-bootstrap-3. Again, this is only the account's UI stuff, this is not the account's itself. As you can see, it's added the accounts-base, because that's a dependency, but it's not going to give us much more functionality other than that. Okay, well, heading over to nav.html, which is our nav partial, I'll just paste in the code from the atmosphere.js page, and it's going to output the login button's partial right here, but it's going to be pulled over to the right. And refreshing, I don't see anything-- I sort of do. If I highlight up in the top right, it says no login services configured. Well that's because when I installed this thing, all we got was accounts-base. That just means, hey, here's the tables and some models and other things, but it doesn't really specify what specific types of accounts I want. So for that, I need to add accounts-password, meaning users can log in using a username and password. You don't have to have that, you can have other services too if you like. Okay, now that that's in here, let's go and take a look and make sure that it was installed, and it was, we have email, bcrypt, sha, and so on, and boom, our UI package has updated itself. Look at that! Neat little dropdown for logging in and also creating an account, so let's do that. I'll just drop in my email address, drop in a password, and create an account. And I'm logged in. That's pretty easy stuff right there. I can open up the console down below, and here I can use a special function called Meteor.user, and there you can see that it's actually a function. Let's pull this up a little bit here. If I want to take a look at the logged in user, I just say Meteor.user and invoke it. There's my ID, I've been saved. I have an email in here. I also have a profile that I can add some interesting information to.
Configuration
This accounts package from Meteor is not a toy, it is actually quite capable. If you head over to docs.meteor.com, take a look at the account's API where can see all of the different things that you can do with it. You can get the current user, you can get the current user ID, you can take a look at all the users if you want. It's got permissions on top of it, it's got all kinds of restrictions, so the current user can't elevate or change any of their information. And if you take a look at this right here, you have different services that you can plug in for a given account, including Facebook, Twitter, Google, and so on. We'll look at that in the very next clip. What I want to do here is I want to change the default configuration a little bit. And you can see some of the options that I have available to me, including send verification email. I really like that. It'll send the email for you and then you've just got to click on, and then our account is verified. I can restrict things by domain, I can forbid creation if I want. In this config, option is available anywhere, but specifically what I want is something to restrict only on the client, meaning I want to update our UI so that it asks for more than just an email, password, I also want a username. Now you can see you've got a bunch of options here, but for right now I want to have just passwordSignupFields to be USERNAME_AND_EMAIL. I have a couple of choices here and I'm not a fan of the all caps string magic here, but that's just the way it is, USERNAME_AND_EMAIL. And you can just drop that into Accounts.ui.config on the client, and that's the important thing. If you put it on the server, you're going to get an error. You might be saying, well, how would I know that? Well, as I showed you before up on the top right, you have the option they tell you if it's client, server, or both. Anyway, here I'm going to drop accounts.js into a brand-new directory called config, and so inside of here I will put Accounts.ui.config, and I will drop in that switch passwordSignupFields USERNAME_AND_EMAIL all caps magical string, hooray! All right, fair enough. So let's go back over to our shop, and I'll refresh, and it takes just a second. So here let's sign out and I want to sign in one more time, and you can see Username or Email is now the prompt for signing in. I like that. But for creating an account, you have three fields here. So I could say robconery and then my email and then my password. This is a good idea in case your users what to change email, it doesn't really matter, it's not dependent on their login. Some people like having a username, including myself.
Github and Google
As I mentioned in the last clip, this membership package is not a toy. Let's add login abilities with GitHub and Google, and to do this, we just add a few more packages, accounts-github, and then next, accounts-google. And this is going to use OAuth to log our users in, and create an account if it doesn't exist. This is one feature that I wish platforms out there would offer by default. I love having membership already built in so I don't need to think about. All right, now that we have Google and GitHub plugged in, let's go over to our site, and I'll sign out, and if I try and sign in again, I have two new options, but they need to be configured. So let's do that now, and I can do it right through the web interface. So if I click on configure GitHub, it tells me step by step what I need to do. So let's go over to GitHub, and here you can see I already have a developer application called Rocket Shop set up for me. There is my Client ID and my Client Secret, so if I head back over here, I can drop in Client ID, and I'll go back over here and drop in Client Secret, and I will leave the login style as popup, which is the default, so I don't need to do redirect. I'll save the configuration, and then now I can sign in with GitHub. And just like that, I am logged in. It's all done for me. I think that's extraordinary. It really makes me happy, as you can tell. All right, let's go ahead and configure Google. This is a little bit more involved. If you've ever had to deal with Google and OAuth, you know that engineers put this whole solution together. So I'll head over to the Google page, and if you open up Google APIs and then head over to credentials, and there we go, there is-- dismiss this message here-- there is our ID for web applications. So you just need to copy this big long client ID and what we'll do now is head over back to our site, paste in our Client ID, go back over, and I'll drop in the Client Secret. Again, step through the settings right here, it is not that hard to find and follow, and there we are. Just have to set the JavaScript origin and redirect, as you see I've done here, and we are good to go. I'll leave it as popup based, I'll save it, and that's all I need to do. Now I can sign in with Google. Popup comes up, and I am logged in.
Role-based Authorization
Let's take a minute and play around here with our brand-new authentication scheme. I can now access Meteor.user as you can see here, and that returns a user's record with an ID, email, profile, username, and various services. And I can also access all the users. As you can see, if I type in Meteor.users, this is a Mongo collection and I have certain things I can do here. So, if it's a Mongo collection, I should be able to query it like a Mongo collection, and indeed I can. I can just say Meteor.users.find, and then I have to call .fetch. And you can see I have four users in the system currently. Now obviously that is a security problem. We'll take care of this in just a little bit. For right now what I want to do is I want to be able to work with a concept with roles. So I want to be able to set myself as an administrator in the system. Now, obviously you don't want all your users to be able to do this, but for right now, I just want to show how this works. I can run an update command right here from the console, and I'll just issue a set to Mongo, and I'll just say set profile.name equal to my full name, Rob Conery, and watch this, this is interesting. In the top right where you see my username, right here, keep an eye on that, because once I hit Enter, boom, I love that. So from this we know that Meteor.users is pub/sub aware, it's reactive, so any changes made on the back-end will be reflected on the front-end and that the account's UI and so on will favor a profile.name. All right, what if we want to now update roles to be administrator? We don't have this in the system now, but if we wanted to add it just by saving, we can't. Access is denied. The reason I wanted to show you this is that the accounts package locks down the Meteor.users collection, so that you can only update a few things, and one of the things that you can update is your profile, but you can't just add ad-hoc information. But since we're developers and hackers, we can go into the Mongo instance backing our Meteor app. As long as Meteor's running, we can go open up a brand-new tab here and say meteor mongo, and that's going to open up our MongoDB instance. And here I can do the same query, Meteor.users.update, and you can see I added the administrator role. Can we access this from the front-end? Well, if I ask for the Meteor user, which is me, the current user, no. You'll notice that I don't have any roles in here. And the reason why, well, it is because again, the accounts package locks down what is given down to the client, so I don't have any roles returned. That's okay, because what I should be doing anyway is searching for a package for roles, and sure enough, there's one by alanning, as you can see here, and this offers some helpers, it augments Meteor.user, and as you can see here, we can add a user to a role, we can check if the user is in a role. We also have some Blaze helpers that we can then ask if isInRole, and then pass off admin or administrator, and then output some information on the screen. And that, as it turns out, is precisely what I want to do. So right here next to login buttons, let's add another list item. And here I will just use that if statement that's right there on the atmosphere page, isInRole admin, and then I'll drop out here a link to whatever the administration page could be. So for now I'll just put in a pound sign, because we don't have an administration page, and it'll just say Admin. All right, then I need to add the package here, add alanning:roles, and that will go in, here we go, and let's check and make sure our server has restarted itself. It has, and I'm getting prompted to turn off auto publish-- we'll talk about that later on. It's a good thing to have Meteor tell us that our site's not safe. By the way, again, we'll talk about auto publish soon enough, but just know this, Meteor's got our back. Okay, so heading back over here, hmm, refreshing. I should see that admin link, because I have roles set to administrator. Yeah, that's the problem. It was Admin. Here we go, Administrator. And if I refresh, it should see that array. And it does, and I am in as admin. Pretty easy stuff. In this module, we plugged in membership, and we did a basic setup and installation of the membership parts and we configured it to make sure we get a username and a password, as well as an email. Then we added some external services, specifically GitHub and Google, but there are more, like Facebook and Twitter. And finally we added role-based authorization using the alanning roles package.
Building
Products Collection
Prototyping is over and now it's time to build out the core of our application. So, in this module, we are going to use our very first collection. We're going to restrict fields and access to that collection, so people can't just save things willy-nilly. Then we're going to do it all again, but this time at top speed. Then, finally we'll take a look at ORMs and some issues that you might run into when you're trying to namespace all of the bits of your application. We do have data, but it's just data coming out of a JSON file pretty much. We need to get this stuff into the database, but there are a number of considerations that we need to think about, because, well, security is a big deal. And if you remember reading about Meteor on Hacker News a few years ago, I think it was in 2012, they had a lot of mixed reactions. A lot of people were jumping up and down, saying, oh my gosh, this is amazing, and other people were saying, oh my gosh, this is a joke! There was no security, and if you had any data in a database, people from the client could just reach right in and delete it, change it, tweak it, do whatever. The deal is, the Meteor team was just trying to show a proof of concept, and the poor guys, well, that's what happens when you push too early. So Meteor kind of got a bad name. Well, nowadays, this is really important to know, nowadays a lot of security is in place, so at the discovermeteor.com blog, as you're seeing here, a lot of great article including setting the record straight on how you can do things without getting into trouble with security and Meteor. It's very important to know these things, and because Meteor basically shouts at you, as you've been seeing, turn off autopublish, turn off insecure, we're going to do those things, I just want you to know that, but what you're about to see is going to look, well, not so secure. So let's just jump right to it; know that we're going to tweak it. Okay, the first thing I'm going to do here is I'm going to rename this big old dump right here to product seeds, and I'm going to set it to be a variable. Then I'm going to declare a brand-new collection, and this is how you do it. You drop it in the lib directory; I'm going to put it into a collections subdirectory, and I'm going to see if we have products in the database. Now notice that since this is in the lib directory, it is accessible by both the client and the server, and therein lies the rub. You can query straight from the client in the same way you can query from the server, and you need that to be secure. All right. Well, what I'm doing here is I am checking to see if there are any products in the database, and if there aren't, then I'm going to loop over the product seeds and jam them in. But I don't want those to be here next to my collection. I'd rather have it be server only, so I'm going to put it in a seeds.js file here under the server directory, so this is server only, which is good. And then down below here, I just checked to be sure that if there's no products that I'm going to loop over and add all the products in, and as you can see, just saving that file restarted Meteor, and our products are in Mongo. It's just that easy. And that's one thing I really like, as long as you're paying attention to all the details and security stuff, and we will be. Okay, so for Products.featured, I am just going to now return a Mongo query. I'm going to take out the find, because we're no longer working with JSON. If I did everything right, then I should be able to now query all of the products down below here in the console, and I can. This is coming straight out of MongoDB. Our homepage should look exactly as it should, and down below here, yep, that transition was easy. One of the reasons it was easy is because I helped myself in the future. If you recall before, I added some methods to products, specifically findOne and featured, because I knew I was going to be working with Mongo later on. I was able to just remove the findOne method entirely, and with the featured method, I just had to replace one line of code.
Restricting Fields
Well, our boss just dropped by and said we have to have a couple more fields here in the database, specifically we need to have some cost information, and also some inventory numbers that needs to go straight in, so I have updated the seeds, as you can see here. However, that leaves me with a bit of a problem. I don't want to show that stuff. For now, let's reload the database, and I can do that with meteor reset. That clears out Mongo. And now if I just start the application again, it's going to see there's no products in the products collection, and it's going to jam in those products; as you can see, it did. Now let's flip back over to the browser, and I'll open up the console down below. And let's query our products and see what kind of information we're getting back. So I can do that by using Products.featured, and whoops, I have to say featured.fetch. There we go. And that'll fetch the actual records. And yeah, whoops, there is cost and there is inventory. I don't want that information sent down to the client. So how can I actually restrict this? Well, Meteor has a few ways that you can restrict access to your collection. There is allow and there is deny, but those are specific to updating and inserting and removal operations, and that's not going to help me. What I need to do is I need to restrict the fields that come back, and I can do that per query. And this gets a little bit cumbersome, because every query you have for products and it needs to do this, but basically I can specify an array for fields and then I can say I don't want inventory and I don't want cost. That's one way of doing it. Flipping back over to our browser, and let's try Products.featured.fetch one more time, and there is our product. And if we scroll down, cost is gone, and inventory is gone. This works well; the problem is, of course, I have to have that field specification for every product's query, but that's okay, I can live with it. We actually have a bigger problem, though. Take a look at the price for the one time reentry to Earth, it's $83,000. Well, what happens if someone realizes we're running a Meteor site, and they say, hmm, I wonder if there's a products collection, and they figure out, yes there is, and I wonder what would happen if I ran an update query from a client right here where I was able to set the price of that ID to 0. Well, look at that. Could you imagine if that happened in production? I'm sure it's out there somewhere. Nothing says fire me please than an oversight like this. So, of course, we need to take care of that. We don't want that ability for our users, because they need to actually pay for these products. We'll take care of that next.
Using Allow
Having all kinds of fun resetting the price on this product here. I'm sure my boss wouldn't really like that. Anyway, let's go and change it back, 123, that's fine. I want to remind you that, yes, Meteor's had security issues in the past. In fact, there's a great quote here in this article, and it's quoted, "inherent security issues." And it depends on how you look at it. Yes, Meteor is wide open when you install it and create a brand-new application; they do that because it's easier for you to get off the ground. But they fully expect you to change things and lock things down and specify what users are allowed to do and what they're not allowed to do, and for that, you have allow and deny methods on your collections. I'm not going to dive too deeply into this. John covers this in his Meteor Fundamentals course, and I highly suggest you take a look at that, but for now, we can play around here with the allow function. First thing I want to do is I want to have a permissions file, and that permissions file is going to be on both the client and the server, so I'm going to put it in my lib directory. I'm going to call it permissions.js. I'm going to create a function called isAdmin, and if you're saying, dude, that's global, I'm going to say, dude, you're right, because that's just the name of the game when it comes to Meteor. I'll have more to say about that later on. Anyway, let's take a look at whether the user's logged in and then whether they're in a specific role. In this case, I want to check and see if they're in the administrator role. So, flipping back over to the browser, let's make sure we have access to this function, and I can see it, there it is. If I run it, nope, it's false, I'm not an administrator. But, we also have another problem, and since I reset the database, I don't have any users now. So, let's fix that, but what I want to do here is I want to actually add a seed rather than having to go log in every single time. So, down below here, I will just add users and same check, if Meteor.users.find.count is equal to 0, then I'm going to go ahead and drop in a brand-new user. And that's how you do it. Acounts.createUser. And specify the username, and admin@test.com, password, profile. I'm also going to create a roles key here, and I'm going to set it to an empty array. Finally down below there, I'm going to use the roles package with Roles.addUsersToRoles, and sending it to admin, which really should be administrator, there we go. Okay, so what I need to do now then is I need to be sure to do a Meteor reset, because I do have products, and I just want a nice clean database. You'll do this fairly often when you're developing out your site. And it's a good thing to do to have seeds and make sure you have good test data in there. All right, now building the application, and good, it inserted our products and it added the Admin user. Excellent. That's what we want to see. So let's flip back over to the browser and if I refresh and go to sign in, you can see we've lost our configurations for GitHub and Google; those are stored in the database, and since I wiped the database, I lost my configuration, which is a bummer. Admin@test.com, admin123 as my password, signing in, and there I am, Big Admin, and my role is picked up as well. Perfect, that's exactly what I want to see. So, if I check isAdmin, I get true back. Again, that's also what I want to see. All right, so this is a tangential, kind of roundabout way of saying we can now go and start working with Products.allow. Products.allow takes a single argument, which should be an object with three keys, and those three keys should be update, insert, or remove, and you don't have to have them all, just the ones you want. Anyway, here I'm specifying that update should only be allowed if the user is admin. Now notice here that I can also interrogate the user ID or the specific product that is going to be updated. Insert works exactly the same way. So I might want to check status, maybe you don't want to allow people to save product that is released, who knows, up to you. Anyway, here I'm just returning whether the user is admin. I can use this here on the server, because once again, our permissions file is in the lib directory, so isAdmin is available in the client and the server. All right, so let's pull back Products.featured, and I'm going to grab the ID off of our one-way-reentry one more time, and I have to do this, because again, I just reset the database. And so let's rerun that update query and see what happens. I'm not logged in, but I do want to see if I can change the price on that, see if I can get it back down to 0, because you know it is pretty expensive, and it's kind of taking advantage of people that want to get back to Earth real fast, don't you think? I'm just trying to help out here, that's really all I'm trying to do. Okay, setting the price to 0, and nope. But did you notice that it changed? It changed on the client, but with a latency compensation it came back and said, no, and it reset it once again to the price that's in the database. This is a critical thing to understand. This is the latency compensation in action. We're going to see this again later on, but look at that, it flashed to 0, and then changed back again. Okay, let me log in, and I'm going to log in as admin@test.com, and admin123 as my password. Signing in now I have administrative privileges. I have successfully hacked into the system and now I can set the price to 0 and that's as it should be. You should be able to come back to Earth at any time that you like.
The Insecure Package
As I mentioned in a previous clip, Meteor comes out of the box with two helper packages that kind of allow you to focus on the business goal of your site, get the prototype up and running, and then as you build things out, you're probably going to want to remove them. The first one is insecure, and as you can see the description here, is allow the all database writes by default. That means from the client you can play with the database and goof around. That can actually be really helpful when you're developing, but you don't want to forget about that. The second one is autopublish, which publishes everything, every collection that's in the database that publishes it down to the client. We'll talk about publish and subscribe in a later module. I'm going to leave autopublish in place right now, because I don't want to have to think about subscriptions just yet, but I just can't allow myself to have insecure be in place, it just gives me the willies. So, I'm going to remove insecure right now. And if we go back over and see if that worked, I want to check two things here. The first is, good, access is denied, that's as it should be. The second thing I want to make sure of is that my allow bits will allow me as the administrator to change the data so I can sign in and notice I am Big Admin again, and if I rerun this update, we're back to 0. Exciting!
Adding Vendors
Now that we've added the products collection and we have some data to work with, let's go through the whole thing one more time, but we'll do it at a bit more of a high speed, and we'll add vendors to the mix here. So, I'll go in and I add a vendors.js file to the collections directory. Next, I'll create a brand-new Mongo collection, and notice once again, we have a global variable called vendors. And we'll talk again about that later on. Okay, so now I have a Mongo collection, and the next thing I want to do here is I want to add some seed data. So if Vendors.find.count is equal to 0, I'm going to drop in some test data, and wouldn't you know, I have it ready to go. IDs, slugs, names, and description. Good stuff. Then I'm going to loop over all of these vendors and I'm going to drop them into the database here just like I did above using Vendors.insert. (Typing) Okay, so let's do a Meteor reset, clear out the database, and then just start up our server, and everything should jam in, our products, our user, and our new vendors, and yep, there we go. Martian Armaments, Red Plant Love Machine, and Marinaris Outfitters, good stuff. Now that we have some vendor information, how are we going to use it? Well, if we scroll up here, you can see again I have IDs and slugs and so on, but each product here has a vendor with an ID of slug and a name, so a little bit de-normalized, but welcome to the world of working with a document database. That's okay. So what I need to do now is let's go over to the product page, and I'll just pick my reentry to Earth. You can see I sort of stubbed out a link here that goes nowhere, and it just says Vendor, but of course, I want to have the name of the company that makes it right here under the product name. So, let's flip back over into our template, and the one I'm looking for of course, is easy to find, because I've named everything in a particular way. Client, template, products, show, very good. Right here is the link that I need to fix, Vendor, and there is the anchor tag. But I'm going to have a bit of a problem. Now right here I can output vendor.name. That's not that big of a deal. It'll show up just fine. However, if I use pathFor and I put vendorsShow, I've got to send it some information and it starts to get ugly. So rather than do that, I'm just going to use a with block here and then it becomes easy. I can say with vendor. And I'll output the name, and then I can use pathFor straightaway. Because inside the with block, this is set to vendor, so pathFor will be able to figure out the route and the parameters that I need, and I will be needing the slug for that vendor, because in just a few seconds when I set up the route, it's going to need a slug so I know which vendor I chose. Speaking of, let's go set up routing, and yeah, if I hover over it-- that's not the right link, so whatever. Okay, heading over to router.js, and let's copy this exactly. And I'll put vendors and then slug. Good. And this should go off to vendorsShow, good, and I'll return Vendors.findOne according to the slug, here we are, simple enough. And that means I need to have a directory called vendors, and inside this, I need to have show.html. Simple enough. And then the template-- this is the important part-- the name needs to be vendorsShow. And for now, I will just do a first level header, and I'll put the name just to be sure we have everything done correctly. All right. And then let's rename our path here to vendorsShow. Good. So, refreshing and if we take a look at the link down below and the status, yep, that's exactly what I want to see, Martian Armaments, Ltd. And the URL is correct. Very nice. I'm sure it didn't surprise you that it worked, right? And let's paste in here some ready HTML. You can again get this from the downloads or from the code up in GitHub. And down below here, I'm actually going to loop out the products for each vendor, as well as the vendor information above, which you see here. That looks pretty good, but we don't have any products. Where are we going to get those products? This just means I need to have a brand-new code behind file, that's what I'm going to keep calling it, show.js, and this is going to be Template.vendorsShow again, this is the important part, that's the name you've got to remember. Products is simply going to be Products.find and this is going to go and query based on the vendors ID, which I will set to this.id. What the heck is this? Well, since I set the data in the route to be this vendor, this is going to be scoped to the data, so this.id. Down below here, then we should be able to spin out the products, and wow, Meteor's way ahead of me, it already refreshed and spun out the products, which I can now click on, and go over to the product page. That's neat. But there's still one thing I'd like to do, which is to have a link to the vendors in my navbar. So again, some ready HTML, we have a dropdown here, and it's going to drop down a list of vendors. Now how am I going to get that data? It turns out that partials can have helpers too. After all, these are just templates. The fact that I'm calling it a partial really doesn't mean anything, it simply can embed another template, we just think of them as partials. Anyway, I can have vendors here, and it can return Vendors.find, and it will return all of them. Notice that I'm not fetch, I'm using find; I'm returning a cursor out to the template, and the template knows what to do with it. Anyway, and here I can say pathFor vendorsShow, it's in a loop, so this will scoped accordingly, it will be scoped to the vendor that's in the loop. So, it'll be able to figure out the link I need, and indeed it does. Good. I'm calling this suppliers by the way, because vendors is kind of geeky, but if I click on it, it drops down, and I can view all the vendors and all their individual products.
ORMs
Well, you no doubt noticed, I am working almost directly with Mongo right here, and it's really not very fun. Now you might be thinking, isn't there an ORM that you can use, or a way to have the concept of a model here? And there sort of is. I could jimmy-jack some models in here using some different things, but it's just not the Meteor way, if you will, so if you want to do something like that, there's a package for that. In fact, there's a number of them, including this one here, meteor-astronomy, and this has validations and associations and all of those things. And if you cruise down here you can see how this thing works, but it's still sort of at an early stage, but you end up orchestrating a lot of stuff with your various model. And it is an ORM, that's just the way it is. So if you like ORMs, you could check out astronomy. There's also minimongoid, and what this does is allows you to have kind of a mongoid experience if you come from a Rails background, you know what that is. But everything's written in CoffeeScript. You don't have to do this, but if you don't, things get problematic. The problem for me is I'm not a CoffeeScript fan, but if you are, and you want to have a better kind of data interaction, then this is something that you're going to want to look at, because it's quite popular and it gives you much more of an ORM experience rather than working with the bare bones Mongo kind of an implementation. There's also the collection helpers, and I really like this. It is minimalist, and what this does is basically just says, here's some methods that I want you to drop onto each model. As you can see here, a book might have an author, author might have a full name or books. You can set this in one place, and I think this is quite helpful. In fact, I know a number of people who simply refuse to work with the bare bones Mongo stuff within Meteor, and I can't blame them. Another tool you might want to check out is graviton. Again, just another ORM, and it does validations and associations and all those things. It's a little bit more wonky if you ask me, but it's also very capable. The documentation is fairly complete, but to be honest with you, I don't really care for ORMs in general. They make even less sense to me when you're working with a document database. So working directly with Mongo, it's not so bad, but I do wish I could just write my own little classes here and have some better logic tied up next to the data, but I can't, and that's okay. But anyway, I just wanted to point out that these ORMs exist, and if you're an ORM person and you like to work with them, they're here.
Namespacing
Well, so far we have some global variables called roles, products, vendors, it's kind of growing, isn't it? And I'm not a big global variable fan, and I'm not exactly happy with the way Meteor just kind of drops these things everywhere. So let's take a minute and talk about this now and decide if we should be upset, because after all, I'd love to have a namespace like Rocket Shop and then .products to kind of keep things out of the global namespace. But is this really a problem? Let's find out. Over here at the Meteor team blog, and they wrote a nice post on namespacing, modularity, and so on, and one of the things they tackled is something I'm going to highlight, because it's a bit of a head scratcher, but to them it makes perfect sense, which I guess is okay. Let's see if it makes sense to us. They basically say their solution is simple. Use global variables as much as you want. Meteor generates a wrapper around your code so they are global only to the app or package that defined them. Not quite sure why that's a good thing or why that's okay. They're still global, as you've seen. So someone else had this exact same problem, and this is a great post, and if you Google it, it's just Meteor and namespace, you'll find this post, it's right at the top of the list, and he found the quote to be a bit of head scratcher as well. In fact, he calls it out right here. So, right. The wrapping things and we're supposed to be okay with that, I don't think so, because if I somehow downloaded a package that did products, for instance, it would just wipe out and collide with my collection, so I'd have a renaming issue right in front of me. It's obviously not what I want, so I would love to be able to namespace the things that I create, so I know they're not going to collide with other things. However, read through this post, this person has tried all kinds of things, including declaring a root namespace using the alphabetical load order, trying his best, it's just a nightmare. And I hate to say this to you, but if you're a namespacing person, and you really, really want to do it, you're going to have to jump through some hoops to get it done. There are a few solutions in this post, but you're not going to see me implement any of them, because the deal with this, namespacing in Meteor is hard. You might be thinking, well, it's JavaScript. It's namespacing, we do this all the time, how could this be hard? Why is this difficult? Well, let's take a deeper look. Normally when you want to namespace in JavaScript, you just declare a variable, and that variable needs to be declared before anything else does, because you're going to attach things to it. Right? We know this. The deal with Meteor, though, is that you don't know when things are going to be declared, that's the problem. Because this load order thing kind of gets in your way. What do I mean by that? Well, here in the server in seeds.js, I just planted a console.log. We want to see when that thing actually loads. And then what I'll do is I'll come in here in the lib directory, and I'll create a brand-new file called a init.js, and in here I'm going to console.log, and it launched. Good. This is happening before any other files in this directory, so we should be good, right? This should be one of the very first things that happens. Then we also have this specialized function called Meteor.startup. This goes off when Meteor starts up, so let's see actually when that is. So I'll just put Startup called. All right, now we saved this file, it should have kicked off a restart on our server, and it did, and there it is. And it launched in lib, that's not a surprise. Seeds launched, that's in our server directory, but that meteor.startup function, yeah, that came last. So, startup means after everything is loaded and the app is ready, that's when startup is called. Well, all right, so it looks like init.js worked just fine, right? So we should be able to say RocketShop equals an empty object and be able to attach our collections to that down below here. So let's try that. RocketShop.Products, and then I will just drop that down below here, and here, and then right there. That should be good, don't you think? So I'll save this, and then let's see what happened. Error, RocketShop is not defined. That's a big fat pain in the butt, isn't it? But wait a minute, didn't the init.js file go, it's the first file in the directory, why didn't it go off? Well, the short answer is that when Meteor evaluates the alphabetical order of files, it includes the subdirectories as well in that evaluation path. So starting from the very top here, let's see if we can explain this. The load order as we know it is that the lib directory is loaded and all the files inside. The server directory, the same things, all the files inside it, client, Meteor.startup is called, and then any file that's called main is called, and it's called last. We don't need to worry about that for right now, but for us, yeah, we assumed that lib was going to be called first, and we were right. However, products was called before init. The reason why is because C in collections comes before I in init. And that's because the alphabetical order evaluation considers the subdirectories as well. So, here's the question you need to ask yourself. When it comes to namespacing, do you want to care about the load order? I'll tell you what, it's just a problem, and I just don't want to consider load order when I write the application, so for now I'm going to punt. If you needed to do namespacing, you could at the top of every single file, do it old school, and just say RocketShop. If it's defined, well, great, if it's not, then we're going to use an empty object. But again, that's a lot of extra code for a problem that really just doesn't exist, so I'm going to leave it exactly the way it is. In this module we added our very first collection, the products collection, and added some helper methods to help us out. Then, we tightened things down. We restricted the fields, we used allow to make sure that users can't just arbitrarily update products and prices, and then we removed insecure to make sure that no one can accidentally update things without our permission. We then went full speed and added vendors to the site. Finally, we discussed ORMs and the trouble with namespacing.
Testing
Testing Setup
In this module, we're going to tackle testing and it's going to help us write our shopping cart code. Specifically we're going to set up our test environment, and then we're going to write the initial test for the shopping cart itself. Then we're going to add a cart page, and finally we're going to add notifications to the site so that when things happen, we can pop up a fun little overlay. Getting to our products page is pretty easy. We can go the supplier's route, as you can see here, and click on it, and boom we're on our product page. Or we can just go from the homepage. And that's neat and all, but what happens when we try and add something to the cart? That's really the goal of our site, we need to sell stuff. Well, it's not quite hooked up, and that's what we're going to do now using testing. So, speaking of, we have a testing tool that we can use, Velocity. It's built right into Meteor, it's pretty neat. We can install whatever framework we like to work with it, including Mocha, Jasmine, Cucumber, or the Robot Framework, so pick your favorite one. For me, that is Mocha. To add it to our site, it is, as you could imagine, just adding a package. In this case, it's mike:mocha, which I think is a great name for this package. So we're going to drop this in and then once it goes in, it's going to restart our application for us, and you can take a look at all the stuff that it added for us, including the velocity:core package and all the things that we need to hook in, as well as the chai assertion library. Now when our app starts, look at this output. Velocity is talking to us and it's saying Mocha is starting in a mirror, so it's basically mirroring our site, and that's what it's going to be testing. So if we back here to our site, up in the top corner on the right side, we now have a test runner. And it's sitting there pulsing at me. That's a little unnerving. So we don't have any tests, and if we wanted to take a look at what those look like, we can click that little button that says add sample tests, and wouldn't you know, it does. This is just crazy. I think this is one of the most amazing testing experiences I have ever seen, but there are problems. It's so easy to get taken away with a cool looking UI, but we'll see how useful it is. All right, so what just happened? It added a test directory for us, and it added a Mocha directory inside that, and then inside of here it added some test files that we can then expand on. So I'm not quite sure what most of that syntax means, if typeof MochaWeb is undefined and so on, but I do know that what it's trying to do is it's trying to separate client versus server. So, inside of here, this is our server test, so I just made it fail, and you can see the way it looks here. It's telling me, it gives me a nice output, and it's saying an AssertionError has failed. All right, that's grand. So I can come back up here and close this, and notice that it's turned red. It used to be green or blue before, but now it's red. If we want to see more information, we can come down here and take a look at the logs. We don't have any logs just yet. We can show the files under tests specifically, and there it shows our two test files that are there. That's great. And then it can actually show you the Mocha instance that is running our site. And look at that, it just shows it in a frame. This can be really important to a lot of people, because if there's a problem with Mocha running your tests, because it is running in parallel here in a mirror, you can see it right there. I haven't actually had an occasion to have to check that, but it is there, just so you know. All right, having changed that assertion back, we now have passing tests. Let's write something for real.
Cart Tests
In the downloads for this course and also up on GitHub, I have a file called cart specs or shopping_cart_specs, I should say, that you can open up and then just drop straightaway in here. I'm doing this, I'm prewriting all these tests because, well, this course isn't about testing, it's about the Meteor testing experience, so I hope you don't mind; you don't want to watch me type tests, it's really horrible. So as we scan down through here to take a look at what I'm doing, there's a very typical way that I like to test. I want to be sure that I can get a cart, that cart has some basic information on it like a userKey, notes, items, and so on, and down below I want to be sure I can add things and remove things, typical shopping cart tests, there is nothing interesting going on here, I swear. All right, so if I save that file, it's going to tell me I have eight tests failed, but I know I've got a lot more tests than that. It gets a little bit funky here because all of the before and after hooks are failing, so it's reporting that in a weird way. Now this is what I meant; this is a beautiful test runner, but sometimes things are a little bit weird. I have a ton of tests in here, and hopefully it should have stopped when the before hook didn't go off, but it didn't. Anyway, the before hook is complaining is that I don't have a collection called carts. That's understandable; we should be able to access that. That carts collection should have a function called getCard on it, and that should take a userKey. And what this is going to do is it's going to try and find a cart using that userKey, and if it doesn't, it's going to return a brand-new cart object. Now, initially when I wrote this and recorded it, I had Carts.insert right here in the collection. This won't work from the client. It's going to cause problems. Now, why won't it work from the client? Because we removed insecure, so it won't allow this update. So, I will remove this later on, just so you know. So here in the before hook, I am using Meteor.call, that is a Meteor method. If you don't know what that is, maybe go have a review of John Sonmez's Fundamentals course, because he talks about Meteor methods. I'll touch on them lightly as we go along. First, I want to have a shoppingcart.js file. This is going to be accessible from both the client and the server, so I'm going to put it in the lib directory. Inside of here, I am going to have specific methods that happen on the server, and these are Meteor methods, getcart, addToCart, removeFromCart. I don't want people playing with our carts directly. I'd like to make this all explicit using Meteor methods, and this is how you do it. First thing I want to do is go back here and turn these comments into code, and I do that by declaring functions here, and this one's going to be getCart, this is the one I'm particularly interested in right now, and this is simply going to return Carts.getCart. And then addToCart, pretty much the same deal. It's going to take a userKey for the cart and it's going to take a sku. And then finally removeFromCart, you get the idea. It's going to take a userKey, a sku, and there you are. The last thing I need to do is I want to have a saveCart function that I can use, because in the saveCart function I'm going to do some de-normalization if you will. I want to make sure that the cart total and the cart count are correctly added up, and that's what I'm going to do, I'm just going to loop over all the items, and there you are. And finally, I'll just do a Carts.update. I'll pass in our filter, and then finally, I am going to make sure that upsert is set to true. That way it will insert the cart if it doesn't exist. Next, I'll just fast forward a little bit, because writing code is fun, but sometimes watching other people write code is not so fun. Anyway, add to cart, here again, this is all standard stuff. If the item exists in the cart, I'm going to increment the quantity, if it doesn't, I'll push to the array, and then I'm going to use Meteor.call to save the cart. All of this code is on GitHub or part of downloads, and you can take a look at it there. All right, let's jump back over to Velocity. And it looks like I've got an error. It's reporting that key is not defined, so let's go fix that really quickly, and that would be in our carts.js file over here, and yes, there we go. There we go, we have a small error there. And hopefully everything should pass. Oh, one more, two more, hmm. Object object Object has no method bySku. I know what that is. That's in our products collection here. I had a helper method in there that returned products by sku. It looks like I accidentally deleted it. So, now that is fixed. That was in our addToCart method, it pulled a product out of the database, and would jam it in. And let's see, now I am up to three. Wait a minute, these things should be going backwards, what's going on here? Hmm, it looks like I have a pretty funky error, expected 36996, what? None of that makes sense. If you find things that just don't make sense, try restarting your server. This is a weird thing, and look at that, just restarting the server made everything work. So, I don't know what the cause of what you just saw was, but I maybe was just going too fast, because as I was writing tests and saving files, things were restarting all over the place, and I might have been going too fast and, who knows. Anyway, just to point out that if you do find problems with the runner as you saw right there, just restart your server. Next thing I need to do here is to be able to call those Meteor methods from the client. So I'm going to use all the same names. Here I'll just say getCart, and then I'm going to return Meteor.call getCart. So I don't have access to the Meteor methods here directly from the client, I have to make sure I write an asynchronous method for that. Okay, so for this, there we go, just to make sure I can reach it on the client, good, and I can, excellent, and it's returning a cart to me. That's exciting. Now I just need to go back and fill out the client side of these Meteor methods. And I'm again going to use all the same exact method names, getCart, addToCart, removeFromCart, saveCart, so on, and once again I have a bit of a bug here, calling saveCart basically defeats the entire purpose of separating things. Oh my gosh, if a user just wants to alter their cart, they can. Set everything inside of the cart to 0 and then I get to send it up. So, yes, Meteor methods provide good clean separation, but it's pretty easy to do something dumb as you saw me do right there. All right, let's wire this thing up. Here I have a button here called add-to-cart, and what I need to do for the products page is simply wire up some events, and for that we use some backbone style syntax to wire the event, the click on the add-to-cart, we want to prevent the default, because that would try and navigate the page, and then what we want to do is just call addToCart, passing along the sku, which I can do with this.sku, because the data is coming down to us from our route, and then here I'll just console.log the error and also console.log the result. I want to be sure that things are happening the way I expect. And clicking on Add To Cart, hurray! I get a cart back, and it's got an item in it. Isn't that exciting? That's exactly what I wanted to see.
The Cart Page
You can add things to a cart, but that's about it. It'd be nice to have an actual cart page where we could remove stuff, change quantities and so on. That's what we'll do now, and for this I'm going to head over to Bootsnipp.com. And I am not a designer, you do not want me designing anything. So I'll search for cart. And if I go down over here to the bottom left, that looks pretty good, and this is all based on Bootstrap, so this will be able to be dropped right in. I think that looks pretty slick, don't you? So, all I need to do is go over to the HTML here, press the button, and just grab it all. That's what I'm going to do. Now if you want the ready HTML with all the templating and stuff, it is in the downloads for this course. It's also in the finished code up on GitHub. So let's follow the pattern that we've been doing. I'll create a directory called cart, and inside it, I'll create a template called show, show.html. Next I'll create a template, and using a tag here, the template tag, and I'll set the name to be cartShow. And again, naming these templates is really important. You want to make sure you have a scheme that is easy for people to understand, so they can go in and tweak the templates as needed even if you're not around. Now let's head over to our router and create a route. I should mention that as I'm doing this that if you end up with a lot of routes for whatever reason, you can separate things out into separate files if you like. You can have a route controller, or you can just have separate routes, you just use Router.route, and if you have like five cart routes, you can put it in a separate file. All right, for this I'm going to use Carts.getCart, and I'm going to ask for the userKey, but where does that userKey come from? It's a global. This is bad news. So we are depending on a global variable just to be there and to be set and ready and configured as we want. This is a bug, a possible bug. So I'm just going to put this in the back of my mind, file it away, because it could come back to bite us. And spoiler alert, it will! I'll tackle that in one of the coming modules. For now, I'm going to take this out of here. It just feels wrong to me to have global variable in a route. Instead what I'm going to do is to create a bit of a code behind here using show.js for our template. And in here, I'll just return Carts.getCart. For some reason it feels a little bit better to me to be using userKey in here. I'm going to have to fix this later on, and indeed I will tackle it in a later module. For now, let's go and wire up our addToCart functionality so that when I do add something, it actually goes and navigates over to the cart page itself. And here is our link for add-to-cart, and if I go into show.js, you can see the click event of addToCart, and right now I'm just console logging stuff. What I'd rather do is use Router.go, which is kind of like response.redirect if you will, and this is going to navigate over to the cartShow template. So heading back over to our site, the page is refreshed, and if we hit add to cart, we should see our cart page, and we do, look at that! It's styled nicely, but it doesn't have any data, so let's fix that. We'll head over to our template, and then using our Handlebars-y syntax, we will loop over the cart items and then output the cart name, and we will output the price, and there we go, okay. So let's refresh this, see how it looks, and boom! Looks pretty good. Those images could be a bit bigger. But for right now, this looks fairly amazing and I'm happy with it. The next thing we need to do is we need to hook up the remove functionality, and I have a button for that, but let's change that to an anchor tag, and I'll add the class remove-from-cart. And back here in our code behind, I just need to wire the event for that class, or the click of that class, and then I can simply just call removeFromCart. There's one last check I want to make here. If the item was removed from the cart and there are no more items left in the cart, I want to be sure that I route off to the homepage. Because staring at an empty cart page is kind of silly. Or I could show a message or something, but for now let's just do a redirect, and that'll make sense. Okay, that looks pretty good to me. Let's see if it works. And the page is refreshing itself, great. And click on the trashcan, and it's gone. Do a refresh to make sure it's gone, and it is. The next thing I need to do is to wire up the change event of this input box, so if the user changes a quantity, then it'll go and update the cart. So for that, I will just do the change event of the input. That doesn't feel right. I'd rather have an explicit class. So I will set item-qty much clearer. Okay, cool. So now for changing, what I'm going to do is just grab the new quantity, grab the name, and if a quantity is 0, I'll just remove it from the cart. Otherwise I will grab it from the text input, parse it, and then call saveCart. This is a problem. If I take the entire cart and hand it over to Mongo and say save this, I'm trusting that the user hasn't meddled with it in some way. For instance, making the price of an item in the cart 0, or a discount equaled at 100%, so this is a bad idea. And you might be thinking, Rob, why are you showing this to me if it is not the right way of doing things? I think it's equally important to show the traps and the problems that you can fall into when building on a site, as well as the right way of doing things. This is an easy trap to fall into. I fell into it. You always have to be thinking about security and what you're sending back to your server. So I will change this in a later module. The last thing I want to show you here is that I am doing a parseInt on the quantity to be sure that it is a number that is being sent back. So there's our saveCart function. It takes a cart and a callback, and-- hmm, we have another problem here. I'm sending this, but this is not the actual cart. This is a cart item, because the change event operates on a single item in the list. So how are we actually going to get at the cart? A few ideas pop to mind, but what I am going to do is just lean into the Meteor thing here and declare a global variable. And if you're thinking, Rob, it's time to turn in your JavaScript card, you might be right. But you know what? I am just going to go with this, because it is the Meteor way of doing things, which I disagree with, but let's just see if it becomes a problem, why not? I'll have a refactoring session in module seven, so I might come back here and revisit this after I set up publications and subscriptions. For now, let's just see if this solved our problem, and I'm sure it did. Let's come back over here to our browser, and I will change that quantity to 12. There's our update. There's our message box. And the price changed. That's good. It works. As you can tell from the tone of my voice, I'm not exactly happy with this solution. However, I know that it is critical and very important to get something working onscreen. It might not be perfect, it might offend my sensibilities, but it works. I will go back and refactor. I have to go back and refactor a lot of things. This is going to be one of them. I rarely build things correctly the very first time, I always have to go back and refactor. So, if this works out and there's no problems, hurray! Maybe the Meteor thing will work and maybe I can change my mind a little bit on the use of global variables. I doubt it. We'll see. But we will address this later on.
Notifications
Well, obviously alert boxes are a horrible idea. I'd rather have a really pretty overlay notification. If you go to atmosphere.js, of course, there is a package or 12 for that. The one that I'll be using for this is juliancwirko, the s-alert package. It's really neat. If you go and look at the demo, it's amazing as you're about to see. The next thing I need to do is to configure this thing, and I've created a file called notifications.js, and I've dropped it into the config directory for the client. Here is the default configuration for sAlert. If you don't need to change anything, you don't need this file, but I do want to change things. I'm going to reset the position to be the bottom. I'll leave the time out at five seconds, and stack I'll leave at true, because I want to stack the alerts. Next I need to give sAlert a place to output. So I've created a notifications.html template, and I'll let Meteor inject this into the body as you see here. This is just a standard way of doing things with sAlert. All right, that's pretty much it. The next thing I need to do is to have some triggers for when I want the alert to go off. So here on my cart page, I'll do a quick check on the number. Let's just say if it's not a number, then I want to check it. In addition, if I have any errors down below on save, and if I have success as well, I want to notify the user. To do that, you just say sAlert.error, or sAlert.success, as you see here, and then you just pop the message. It's as simple as that. Not all that much configuration to have this. I think this is beautiful. And it took me all of a minute or two to configure and set up. I think that's pretty neat. Again, one of the major strengths of Meteor is the community and the package system. In this module, we set up testing with Mocha for our shopping cart. Then we added tests and made them all pass. We then rounded out the cart experience adding a remove and update quantity feature. And finally, we added notifications for a little bit better user experience. Now if you're wondering, wait, wasn't this module supposed to be about testing? And yes, it is. However, it is so easy to test with Meteor, I pretty much showed you what I needed to show you in about 10 minutes. And as I keep saying, that is one of the best thing about Meteor. If you want to do something, like in our case, testing, there's always a package out there for you. We wanted to use Mocha, so we dropped it in and wrote our test. Since you're an experienced developer and you've no doubt used Mocha before, I don't really need to show you how to use a framework or how to test in general; you already know how to do those things. I just needed to show you how to plug it into Meteor, and that's what we did.
Checkout
Installing the Tools
In this module, we're going to up the complexity by tackling the checkout process. This is a complicated affair. There's a lot that goes into it. We're going to start by installing the tools that we'll be using, which are the Stripe library for the Stripe payment processor and Postgres. We're going to add routes and build our page using Knockout JS. Then we're going to use NPM, go right around the Meteor Framework to work with the Stripe and Postgres libraries. Finally, we'll finish up the checkout and then we'll add a receipt page. Our product pages are showing the way we want, our cart page is working rather well, it's now time to get that checkout button to work so we can actually buy something, which is the whole point of what we're doing. For this I'll be using Stripe, specifically Stripe Checkout, which is this amazing JavaScript checkout experience. This is a little modal popup. It pops up, asks the user some billing information, including address, card number, and so on, talks to Stripe directly, keeps you PC compliant. It's really easy to set up and configure, and we'll be using it today. The next tool that we need to use is MassiveJS. This is a Postgres library that I created a few months back. I am not going to put sensitive sales data in MongoDB, not a chance, so I will be using Postgres for this, and why not use a tool that I created? It's MassiveJS, a dedicated tool for Postgres or data access. Next we need to work with NPM so we can use these tools, both Stripe and Massive, and I'll be using Arunoda's meteorhacks:npm package. This is an amazing tool that allows you to work directly with NPM because Meteor does not. Actually, I shouldn't say that. Meteor allows you to work with the global NPM packages, but you can't have NPM packages per project, which is a bit of a pain. Finally, I like Meteor's ability to orchestrate the UI, but I want to do something a little bit better, so I'll be using Knockout, going right around Meteor, using a library that I really like. That's what we have to do for this module. Those are the tools that we will be using.
Routes and Templates
We've been through this drill before. We need to add templates and a route so our checkout page shows up. Let's do that now. Repetition is a good thing when learning a new system. So, first thing I need to do is to create a directory called Checkout, that's where I'm going to keep my templates. I'm going to have my template name reflect these locations on disk, so it's going to be checkoutShow. There we go. And inside of here, I'm just going to simply put a first level header to make sure that the page shows up as we expect, and I'll just say CHECKOUT, hooray! All right, now that we've done that, let's go to our router and we'll add a route for checking out. We don't need a parameter here, because a checkout page is going to use the current cart, so we're all right just leaving it exactly like this. Okay, we'll set the name to checkoutShow, and we are ready to hook it up. So, let's head over to our cart page, and what I can do here is I can set the click event of the checkout button and I can say Router.go, but it's so easy to get stuck in that mindset, you don't need to do that. All we need to do is just set a link. There's no need to trap an event; let's make things easy on ourselves. So, we'll do pathFor 'checkoutShow' as you see here, and that'll take us to the checkout page, ideally. So, let's go check out and there it is. Checkout. Very good. Creating a template, setting a route, linking to that route, we've got that down. We've done it enough times so far. So let's drop in the HTML we need for our checkout page. You can find this in the course downloads, or you can grab it from the GitHub project up on GitHub. Here you can see it is a typical checkout page, sort of. I'm doing something a little bit interesting in here, which is that I'm working with Knockout, and the reason why is the templating system for Meteor is pretty good, but I want to go beyond that. I want to do some interesting whiz-bang stuff, and I'm going to do that using Knockout and all the data bind syntax. I haven't hooked it up yet, so our page is kind of broken, but hey, look at that, at least it's showing up. That's a start. In the next clip I will hook up Knockout and the view model.
Knockout ViewModel
If you've never used Knockout before, this might be a little bit strange to you, but I'll try and explain it as we go along. I need to create a view model that Knockout is going to work with, and to do that I'll drop in the checkout view model right into the checkout directory to keep it all together. And to remind you, I have Knockout and Stripe checkout in the scripts directory here, so they're already part of the project, and I can just use them directly right here. I'll create a variable called CheckoutViewModel, and this is standard Knockout stuff. I have some properties set above as observables, name, email, and description, which is computed, and then I have a shipping address down below, and I'm looping over the keys of any arguments passed in, looking for an address. And if it's there, I will assign the shipping address. In fact, I'm using the arguments for the name and the email as well. Down below I have a set of Boolean flags that I'll be using to update and change the way the page looks. Specifically I need to know if the user has accepted the terms. And then I have two methods here, showCheckoutForm and showProgressBar, which you can see. These are going to set in the background, because what I'm going to do is I'm going to switch out whether I'm showing the form or that nifty little please wait a minute while we redirect. Down below that, I have some simple validations that are going to tell the user what they need to fill in and so on, I'll talk more about that in a bit. Next up here is the loadStripeCheckout event. This is one of the more important things. This is going to interact with the StripeCheckout library. First thing I need to do is to configure Stripe itself; that is our handler. I am setting a key that I need to send along and that is going to be set in Meteor settings. We'll talk more about that in a little bit. Next I have an image that I want to set for a little branding at the top of the little checkout box. I want the zip code from the user, and I want to preset the email using the view model's email field. Then, allowRememberMe I'll set to true, and I will set a flag here that I want to have the user have to put in an address. Finally, I will tell it that when the token comes in it needs to fire the handle token function, which I have defined down below. Finally, to finish off the click event, I'll just call handler.open, and it opens up the Stripe checkout box with the name and the description and the amount. After the user has put in all the information and hits submit, it's going to bounce out to Stripe. Stripe's going to cycle it and send back a token if everything is successful; otherwise, the user will get an error. When that token comes back, this handleToken function will be fired. I'm going to scroll back to the top of the window, and then I'm going to turn off the checkout form, showCheckoutForm false. That's going to show the redirecting status bar. Then, I'm going to construct a checkout object. This is the data that I am going to send to the server. I want this to be in a very specific format that you see here. I want name, email, description, IP, I want to send in the shipping address, as well as the billing address. In addition to these things, I'm also going to send along the full token, as you can see here. Finally, when all that's done, I'm going to call processPayment. This is going to be a Meteor method, it's going to hand that checkout off to the server, and then we're going to evaluate the result. If everything is good, we will redirect off to a receipt page. Okay, checking out our page, you can see that everything is broken. I don't have Knockout hooked up yet, it's not taking in that view model, so none of the data binding stuff is even happening. That's okay. I just want to make sure that I don't have any script errors, and I don't. Here in blue, you can see the prompts that I want to show the user interactively as they fill out the form. I don't like red form validations. I kind of find it to be a little bit offensive. But you're filling something out, and you think you've done it right, and the form's like no, you suck, you didn't do it right, go back and fix it. Anyway, so I'm going to use Knockout to control the flow of that. Okay, onward. So, what I need to do now is to have a bit of code behind for our checkout page, and for that I will create a show.js file. And then inside of here, I will create some helpers for our checkoutShow template. Specifically I need to have the cart, and in this case, I will need to use a global variable, because I'll need to use the cart information inside of Knockout. So I'll do the same thing that I did before with the cart page. The next thing I need to do here is to instantiate that lovely CheckoutViewModel that I just created, and I'll do that in the onRendered event of our template. This is a way to make sure that the template is in the DOM before you have anything else happen. So inside here, I'm going to new up our CheckoutViewModel and I'll send in some arguments in the form of test data. This has a test name and an email and address and so on. Next I need to grab the DOM element that contains all of the Knockout bindable bits, and the reason why I need to do that is because since we're working with Meteor, there is no page refresh, so if we do a Knockout binding, it's going to be remembered, and if we come back to this page, all those bindings can get fired again and we're going to have an error. So, to get around that, I am going to grab that DOM element and then I'm going to use ko.cleanNode, that's going to clean off all of the bindings that are already on that DOM element. Finally, I'm going to reapply the bindings. Now, I don't know of a better way of doing this. I've looked all over the place and how to deal with the error, and people would say, oh, there's a better way of dealing with it. I never found out. People would say there could be a performance problem. Well, frankly I haven't seen it. So, this is the best solution I came up with. If you know of a better one, please do let me know in the comments. All right, having done all of that, let's go back over to the browser and see if our load up is working and that Knockout is actually firing. Go over here, yep, that's looks pretty good. So all the bindings are going off, the rendering has happened as we expect. Here you can see their little prompt. But if I take away the shipping address and go down below, you could see the prompt grows. We still need your shipping address. If I go up here and remove name and email and go back down below, we still need your name and email. So the prompts are firing and loading as we would expect. If we put this information back, actually I'll just refresh the page and go back down, you can see that all we need is for you to accept the terms and conditions, and once you do you get a green box. Good to go.
Meteor Settings
Previously in the last clip when I was going through the checkout view model, I mentioned that I would talk about Meteor settings, because that is how I'm setting the Stripe key. This is very important. You want to be using Meteor settings for this kind of thing. You do not want to hard code this or come up with your own way of doing it. So down below here I have two files, settings.development.json and settings.json. And if we take a look at settings.development.json, you can see we have a few keys in here, stripeSecretKey, which I am going to erase and redo, and our stripePublickKey, and what these are are just keys if we're working with test data up on Stripe. In settings.json, this is going to be for production use. So the development is for development and this is for production. Anything that you label public is viewable by the client. If you don't have it under public, it's only viewable on the server, and that's really important to understand. In my case, I need that public key on the client, so I set it under public. All right, why are we doing this? The primary thing is you want to have these settings file ignored. You do not want sensitive information stored anywhere ever, even if you have a private GitHub account, or you have a private repo of your very own, do yourself a favor and do not check things in, they can be discoverable and when you least expect it. Okay, off my soapbox now. How do we actually use these settings? You have to tell Meteor to load them. I think this is a little silly. I think if there's a settings.json file, it should be loaded, but it's not. You have to specify where the settings are, and I guess that makes sense. So, here I'm going to tell it to use the settings.development.json. When I go into production, I'm going to tell it to use settings.json. Actually Meteor up is going to do that for me. We'll talk about that in the next module. Okay, now that I've done that, my settings have been loaded, so I can use those settings now back on the checkout page. And again, when it comes to production, we'll load those settings from our settings.json file, and those will be used in place of our development settings.
Using NPM
I've created a Postgres database here, and a table called checkouts. And you can see the SQL for it right here. You can grab the SQL in the project downloads, or again you can get it from the project up on GitHub. Here I'm just creating a checkouts table that almost exactly mirrors the structure of our view model. I have an ID, I have a card ID, email, name, description, items using jsonb, same with the billing address and shipping address using jsonb. I'm going to save the token, once again as jsonb, and the payment details, which is going to be the Stripe response, once again, jsonb. I love Postgres. So we're going to be using NPM to save a query to the checkouts table directly. However, it's worth mentioning that you really should be using a message queue for this kind of operation. Ideally make it asynchronous to the background, send an email when the order is ready, or make it synchronous if you want, but have some fault tolerance. Message queuing is a good idea, I just need to say that right now. Anyway, so what we're going to be using to make this all happen is the NPM package from meteorhacks. Using this thing is kind of interesting. Installation is simple, and getting your packages in is also simple. All you have to do is add meteorhacks:npm as you see here, and then to define your packages, you simply need to fill out a package.json. And this is not to be confused with the node modules package.json, this is just a file that's a JSON file. However, when you want to call and use anything from NPM, you have to use Meteor.npmRequire and then you have to wrap it with this Async.runSync, because as you've noticed, everything in Meteor likes to pretend its synchronous, and in this case, NPM stuff usually has callbacks like ours do, so you need to wrap it with that Async.runSync. Now it looks a little bit hacky, but once you do it once or twice it's not all that bad, and we're only using this in one little spot. Okay, NPM has been added and it's telling us down below here that a packages.json file has been added to our project. We can go in there now and we can specify the node modules that we want loaded. And then, of course, we need to reload our project. Sounds good, fairly straightforward. If we go over here, there is a packages.json, which is an empty JSON page. And what I want to do in here is specify that I want to load up Massive, and you have to have a version in here. If you don't know the version of the package you want to load, just go and read its package.json up on GitHub, or at NPM.js.org. So for Massive, this is going to be version 2.0.4, for Stripe it's going to be 3.3.4. You can also put a GitHub address in there with a specified tag if you want. All right, so let's restart our application, making sure to send in our settings, and we should get a message, there it is, npm-container, it is now grabbing Massive and Stripe and it's going to drop those NPM modules for our use later on. And there it is, it didn't take too long, and we are running. No errors, that's excellent.
Stripe and Postgres
Now that we have access to our Massive and Stripe packages, I want to set up a little bit of abstraction so I can work with them in a same way. I'll set up a little repository for our sales_repo. This is just going to be Postgres for now, that one simple table. And on Meteor.startup, what I'm going to do is I'm going to declare once again a global object that I can use throughout our project here. The next step is to require Massive and to load it up. When Massive loads up, it actually looks at your database, reads in all the tables, and attaches them to an instance that you can use later on. I should also mention that there is a loadSync method that doesn't take a callback. I could use that here, but using the callback works just fine. So we start out by requiring Massive, and then I have to load it up, so I have to say Massive.connect. That is going to give me an instance. Then I'm going to set a db variable, as you see here, to the result. That result is going to be the result of the callback. Then, down below I have some additional methods, save, checkout, and getReceipt. Once again, this is going to use Async.runSync, and then I'm just basically wrapping up checkouts.save. Checkouts is our checkouts table, I could just call save and pass in an object and it will do the rest for me, which is nice. And then I just need to call done. Once I call done, that's going to pass back to the save result there, and that's going to pass back the results of our query. For those of you not familiar with Massive or if any of this looks weird to you, this is just basic database interaction. I'm not doing anything tricky here. The only thing that's particularly tricky is I'm wrapping all of these functions with Async.runSync, and when I pass done back in, that is going to return out a result in a synchronous way. That's really all you need to know right now. That and I also have a global variable called SalesRepo, and I can SalesRepo.saveCheckout, which I will do in just a little bit. All right, the next thing I need to do here is to create a little wrapper for our Stripe service, and I will call this stripe_service.js, again, putting it in the server folder. I don't need to access any of this on the client, so I want to make sure it's locked away on the server. Inside of here, I'm going to do the same thing. In Meteor.startup, I'm going to create a StripeWrapper prototype. This is typical Stripe API call stuff, once again, wrapped in Async.runSync. You'll also notice here that I'm using Meteor.settings to grab the stripeSecretKey. These are not public, but they are viewable on the server, not viewable on the client. That's important to remember. Down below here is your typical stripe.charges.create. I'm just passing in a charge object using the token that was passed into us with the checkout object, and then I'm going to return the results of the call. And a little extra credit for you, if you want to, we need a refund charge method. You can go ahead and add that if you like, and make sure you email me, and I'll make sure you get the extra credit. All right, this looks fairly complete. One thing I have to say one more time when doing things like this, saving records, running charges in sequence, you really want to have some kind of message queuing system. If you're familiar with RabbitMQ, you can plug this in instead of plugging in Stripe and Postgres as I've done here, I'll leave to you, and also for an additional course later on. All right, we are ready to go. I have my Stripes service public variable down below, I've got my SalesRepo ready to go. I could now start saving and running checkouts. That's exciting, we're getting to the heart of the matter. That is coming up in the very next clip.
Checkout Execution
Well, we left off on the client last time by saying that we're going to call process payment after the Stripe checkout bits come back. I still have yet to make that function, so let's do that now. I'll create a brand-new file here and call it payment.js and stick it in the lib directory. The reason is I need this accessible by both the server and the client. However, I only need parts accessible from the client, and I do that by saying if this is the client, then allow these methods, and if it's the server, then I want to declare these methods. This is a nice way of keeping logic together while separating client and server. We did this already for the cart page. Okay, so what I'm going to do is create a few methods here called processPayment and getReceipt. We'll talk about getReceipt more later on. processPayment is what I need to call when the token comes back from Stripe. That's going to call a Meteor method by the same name, process payment, which is down below. This method is pretty straightforward. We're just going to grab the cart out of the database, and we're not going to accept it coming in from the client. That's not a good idea for all the reasons that we said before. So we're going to go grab the cart out of the database, then we're going to set the total on our checkout from the cart itself. We're going to pass along the items, and this is important, you have to stringify all the cart items; otherwise, the jsonb stuff in Postgres is going to strip it off. It's just a weird thing. The jsonb data type does not like an array. As a root level object, it wants to see a label there, that's what it considers proper JSON, so we're going to have to stringify it, which is okay, it goes in still as jsonb. Next were are going to set the payment details using our StripeService.runCharge. That is going to bring back the entire response from Stripe that is called right here from our Stripe service, as you can see, stripe.charges.create. That's going to be a whole mess of data that we can access and use later on, and I want to make sure I trap all of it. So that's where that comes from. Then we're going to set a reference key using a handy Meteor helper method called Meteor.uuid. This is just going to be a GUID that we'll use later on as the receipt ID. We're going to empty the cart and then we're going to save it into the database. If things don't go right with our saveCheckout routine, you'll see the response we'll get back. We'll get back this object that says success is false, and it'll pass back the error. If everything goes well, we'll get a receipt ID, which is going to be the reference key, and then a little message that says thank you, and success being true. That response object is going to make it all the way down to the client where we can interrogate it. If we have success, we are going to just redirect to receiptShow. This is a route that we haven't made up just yet. And then we're going to pass along the parameter ID. However, I want to see the results straightaway in the console just to make sure I know what's going on, so I'll just console.log the result right here. If we have a problem, then we're going to pop up an S alert as you saw down below. Okay, I think we are ready to take this thing for a spin. Let's go back to our checkout page, and it looks like we have all the data that we need. The only thing left is to accept the terms and conditions, there we go, and I hit pay now, and this should launch the Stripe checkout box, and it does. Branded nicely with our name and our logo, that's cool. I wish I had a little more real estate here to show you this box, it is so nice to see, but I have small screen. So notice up here on the top right side, it says we're in test mode, that's what we want to see. And what I'll do is I'll just prefill some of this stuff in Joe Blow from my Chrome form filling stuff. And here let's put in a test card of 4 and all 1s. Stripe has a bunch of test codes that you can use if you want to see what happens when a charge is declined, and so on. Down below the total is on the button. Clicking it, and yeah. I love this, this is all Stripe's doing. This isn't any of my code. I just told Stripe what to do. We get back a success object, that is great. That's telling us that our data went into the database, it's giving us a receipt ID. Let's go and verify that. Let's go and take a look at our checkouts table, dropping this thing open, and there it is, there's our checkout, the reference key matches. And taking a look, and let's just verify that that GUID is the same GUID here; it is. Good, so that is going in. So let's go check out all the data that we have. We have a cart_id, good, we have created_at, good, email, name, IP address, we pulled that off of the Stripe information that came back, this is great. All this information, the only thing that we have from the user is their shipping address, name, and email. The rest of it is information gathered by Stripe, which I believe we can trust. Yeah, this is great. All right, so it looks like we have all the information that we need here, our checkout is going in, the only thing left for us now is to get a receipt page going so the user can see their information about their order.
Receipt Page
Our checkout information is being stored in the database. We are getting a redirect prompt, which is great. Now we just need to create a receipt page so the user can see their order. So, down to business, it's the usual thing here. Create a directory called receipt, and in it we're going to create two things. First is show.html, which is going to be a template, and the next is I'm going to need a code behind file, so I'll create show.js. And for our template, we have some ready HTML once again in the downloads of the project, also up on GitHub, go and grab it, and drop it in. And here you can see that I'm using a with block, and this is important, why am I doing this? Well, the main reason is that if we scroll down to the very, very bottom, you can see I'm using each, which is a really neat thing about the Handlerbars inspired templating language that we're using with Meteor. Else gets fired if we don't have a receipt object. So here it's just telling me that there's no receipt with that particular ID, and then it's offering me a link to go buy something else. So that's good, that is why I'm using this with block. Okay, we have our template, looking pretty good. Now let's go over and create a route for this receipt, and I need to be able to accommodate people who maybe don't have a membership account with our site. So I'll want to be able to pass in the GUID token ID, so anybody can view their receipt without having to login or have an account. All right, so let's give this the name receiptShow following our typical pattern. And then here, I'm going to pass in some data. But here we actually run into a bit of a problem. You wouldn't think it would be a problem, so let's dissect this a little bit in what we're trying to do. I have a getReceipt method here on my SalesRepo, and this just takes a reference key, which is that GUID. That is going to run our Async.runSync, so in other words, it's going to make our database call synchronous. All right, so then I'm going to say checkouts.findOne, which basically says go find the checkout with this reference key, and in fact, I'm going to console.log the result right there in line just to prove to you that it is working, but something weird is happening. So here I need to use Meteor.call getReceipt, because the router is on the client, and so I need to be sure that I am calling this server method using Meteor.call. And I'm passing in this .params.id. And that should work. Now all you should have to do is say return and then set a key for receipt, and then set it to a value, the receipt itself. That should work, shouldn't it? It's a synchronous call, I should be able to have the data. Well, let's check it out and see what happens. I'll head over to Postgres and open up our checkouts table and grab that reference key that we just jammed in there. And then I'll head over to the browser. Now that we have our route, I should be able to put in receipt and that ID. And look at that. There is no receipt with that ID. That's interesting. As I said, something weird is happening. Let's go take a look at our terminal. And the terminal has output returning now 8. That's that console.log statement that I put in our SalesRepo, so it is returning, but why isn't it showing up on the page? Let's see if we can find out. I'm going to console.log the receipt right here in the route. This is going to rebuild the application, and we're going to go back, and so the page already requested the receipt, because it reloaded itself, and so you can see returning now 8. Let's reload it one more time and go back. Yep, look at there, console.log says undefined. We did the console.log in the browser, that's why we saw it down there in the console there. The receipt is undefined in the route, but here it's clearly not on the server. All right, I'm laboring this a little bit I guess, but basically the deal is this. The data that is expected in this route here is reactive data. If you're using Mongo and you're using a cursor, that is reactive data. A simple call like this is not reactive data, it's just data, and Meteor does not want to wait for it to come back. So it's just going to render the page. The idea with Meteor is that the page will render and then the data will show up when it shows up. In this case, we don't have a facility for that. I'll tell you right now there's a lot of ways that you could try to get around it. There's a package called reactive var where you can set a reactive variable. I tried to set that as the data, eh, it didn't work. I tried a whole lot of things. I spent a good six hours on this one problem right here, pulling my hair out; I wish I had a better answer for you, but basically the deal is this. I am going to return the ID, that's all I really want as data on the page, because I'm going to handle this all on the page itself in the code behind of our receipt page. So the first thing I'm going to do is I'm going to create some helpers for our receipt page. And in here I am going to do what you've seen me do before. I'm going to create a function called receipt, and that function is simply going to take some data and go and request the receipt from a Meteor method. You might say, how you doing that? Well here in our payments.js file, I do have a client method called getReceipt. That is going to do a Meteor.call getReceipt, and it's going to have a callback, which is a problem. How am I going to work this with a callback? This right here is again expecting to work with reactive data. Oh, boy. What am I going to do? Well, I'll tell you what I'm going to do. I'm going to do something a little bit hacky. I'm going to create a variable called receiptData, and this is going to use Blaze.ReactiveVar. This is a reactive variable. It means if the data changes in this variable, it will ripple out that change to the screen. Blaze is the templating engine, in case you're wondering. So, instead of doing a getReceipt, which takes a callback, and I can't use callbacks in here, instead of doing that, I'm simply going to return the receiptData variable. That is a reactive variable, our template can use that just fine. We can change it as we want to. So what I'll do is use the onRendered function here. What this is going to allow me to do is it's going to allow me to grab the receipt ID that was passed down to us from the route. And then I can call getReceipt. So, I'll use this .data.id and in here, callbacks are fine. This'll just get fired whenever the template renders, and whenever it's done I will set receiptData using receiptData.set, and I'll pass in the result. I know a little bit secure it is, but I find this pattern quite useful. We're able to use reactiveData, we're also able to use methods that take a callback. All right, so let's try it. And wouldn't you know it, it doesn't work. Oh, boy, these kinds of things just happen to you. You know, we're getting so close to the end, and wouldn't you know, it's the very simplest thing that we spend the most time on. I spent so much time on this, and I thought, hey, we're almost done, this is easy. All right, what's the problem here? Let's console.log this .data.id. What's going on? Something is not being set properly. I'll refresh this page, taking a look at the console down below. Undefined. That's weird. It seems like everything I'm trying to set on this route is undefined, what is going on? Okay, deep breath. I'm looking for this.data.id and it seems to be undefined. Let's go check the route and see what the data looks like that I am sending down, and I'm-- ick-- sending a naked GUID down, that's the problem, I'm not setting an object with the ID field set, oh geez. Rob! Okay, refreshing. I should have some data, and I do. There is our lovely formatted receipt. Success! I love it! Finally, a nice checkout process with a receipt page end to end. I think we are functionally finished here. That makes me happy. The only thing we have left to do is to go into production and show off our good work. And we'll do that in the very next module. In this module, we installed all the things we would need for our checkout process, including Stripe and Postgres. We used Knockout to drive a friendlier checkout page. I could've used the Meteor templating stuff, but I think Knockout did us right. We used NPM to work with Stripe and Postgres, wrapping up a repo and a Stripe service object that I think are pretty functional. And finally, we went into the weeds a little bit with our receipt page, and discovered some interesting things about reactive variables, reactive data, and our NPM stuff. I think we did a good job, and I think our checkout is complete.
Production
Autopublish
Our site is feature complete and ready to go, good job! Nice work. Now we need to push it into production. Before we do that, we need to fine tune and tweak things, and then dive into publish and subscribe to make sure that the data that we're sending around is okay to send around. Then, we're going to get our site and server ready with MeteorUp. Finally, we're going to take a look at hosting services to make deployment easy. You've probably noticed as we've been coding along here that we keep getting this weird message from Meteor that we have set up some data subscriptions with Meteor .publish, blah, blah, blah, turn off autopublish. It keeps prompting us to do that, and the reason is, which I mentioned before, that autopublish and insecure are enabled by default, and that is not a good idea if you want to go to production, and we obviously do not want that. So if we do a Meteor list and take a look at all the packages that we have installed and enabled, you can see that we have autopublish in there and that is publishes the entire database to all clients. We clearly do not want that, because our database, well, some of the data we probably want to hold back. If you're familiar with Meteor, you know what I mean already, but if you're not, let's just take a quick detour here. If I say show me all the products, Meteor will. It's not that big of a deal. But do I need those products front-loaded for everything? What if I had 1000 products in the database? Well, you could see the problem here. But more importantly, what if I do Carts.find().fetch()? Here I can grab all the carts in the database, and that is not good. That's a bit of a security issue more than a data load issue, so we have a couple things we need to figure out here, namely that we need to stop publishing all the data straightaway. I could take a look at everyone's cart and see what they're going to buy. That's not so good. So let's get rid of this thing. How do we do that? We just say meteor remove autopublish. And this is going to be quite easy, it's just going to take it right out, and boom, it's done. However, we have a problem immediately. If you've used Meteor before, this is going to be no surprise to you. If we say Carts.find().fetch() now, no carts. But what about our products? If we do Products.find().fetch(), we have none. That's because we're not publishing any data at all from Meteor, and if we come down here, not even the featured products are making it through. To understand what's going on, let's take a quick detour into the world of publish subscribe. Let's take a quick detour here and discuss a fundamental aspect of Meteor, publications and subscriptions. So far I've only discussed this topic in passing, but we're at the point now where we need to know it in depth if we're to secure and tighten down our application. The main thing to understand is that Meteor wants to be real time, meaning that changes in the data in your application are immediately reflected to your clients. You've probably seen the demos where you update the data and the client, and it's immediately reflected on the page. This isn't all that special. Ember, Knockout, and other frameworks do two-way binding as well, but Meteor goes beyond that. If I update the record in the database, you can see the change is immediately pushed down to the client, which is great. But how is this achieved? The short answer is through publications and subscriptions. In Meteor when you set up a publication for your data, you're making it available to any client that wants to subscribe to it. So far, we've had autopublish turned on, which means that all the data in our database was available for a subscription. In addition, autopublish set up subscriptions for this data for all clients. This means that any changes we make are automatically rippled out to all clients. This makes good sense when you're building your application, and it keeps you focused on setting up functionality, as we've been doing here. But now that we have a working prototype, we're going to want to be sure to lock things down a little bit. As you can see from this screen shot, we have two problems. The first is that all the product data is sent down the wire to our client in a payload. That's overkill and can be a massive scaling problem. The second is that all of our carts are visible too. That's a bit security hole, as well as a scaling problem. We'll need to limit these things to make our application more efficient and more secure, as well as more responsive. If you've ever worked with a single page application, you know that they can tend to be just a little bit chatty, calling back to the server constantly for data loads, saves, and updates. If your server is under load from a ridiculous amount of Ajax requests, which can easily happen, just remember the healthcare.gov debacle in the United States when Obamacare went live? Oh, man. Your user experience can nose-dive quickly-- your server can actually shut down. With a real time application like ours, would our app be running Ajax requests constantly? Would this crater our server? The short answer is no. It's also yes, but really it's no. It's kind of both. Meteor has some tricks up its sleeve to mitigate chatty applications, and also to make sure your users don't see a slowdown. This is where you get to know a lofty little term called latency compensation. What this means is that Meteor will make up for the fact that your server might be a long way away and waiting for data to come and go. Latency is a bummer. So Meteor compensates for it with latency compensation. We talked about this a bit in the first module getting your head around Meteor, but it's worth bringing up again now that we're about to get into production and we're trying to tweak our publications and subscriptions. Whenever a subscription is created and used, the data is mirrored on both the client and the server. If that data changes on the client, the affect is immediate on the client. In the background, Meteor synchronizes the data on the server. If there's a problem, the data from the server will win out and the client change will be reverted. We've already seen this with our products collection when we implemented allow and deny. If there's not a problem, everything proceeds normally. This gives your users a sense of speed, but it's important to understand that changes in data may appear immediate, but can take awhile to actually synchronize. On slow networks this can be problematic, especially if your users make changes and instantly close the browser; that's not good. So we've already taken a great first step by turning off autopublish, but now we have no data at all to work with, which is okay, because it means we now need to think about what data each page needs, and then to prepare a specific publication and subscription setup for that data. That's what we're going to do now.
Publications and Routes
Autopublish is turned off and we have no data, so let's fix that, and the way we do that is by setting up a series of publications that each page is going to need. So we'll do that on the server. That's important. We don't want to set up publications on the client. So we'll just have a single file here called publications.js. And in here, I will simply add a quick method for publishing the featured products that we need to see on the homepage. And I'm going to call this publication featured-products. No need to be tricky here. This is simply a function, and what this function needs to return is the data for this publication. So here, I'll just reuse the Products.featured method. And there it is. That's what a publication looks like in Meteor. It's that simple. But then how do we actually subscribe to that data? We can set up in our route here. There is a subscriptions key that we can use, or we can tell it to waitOn, which is what I like to use, because what waitOn will do is it will wait until the data is actually ready and then the template will render. So here we're just going to say waitOn, and then we're going to see Meteor.subscribe, and then we're going to use the exact name of our publication, and then pop that in right there. And that's all we need to do. This is a very typical publication and subscription pattern that you see a lot in Meteor with publications being set, and then waitOn being used to return in Meteor.subscribe. All right, well how is the data actually getting pulled? That's right here. Don't confuse Meteor.subscribe with the actual data query. That's what I did when I first started getting used to Meteor. Now, the query is still happening here in our homeIndex helper functions, which is as it should be. All right, so let's go back to our homepage, and boom, there is our data back. That's a good thing, because that's the only three products that we need on this page, we don't need to the entire product drop. But we don't have any vendors. All right, well, that's easy to fix. Let's go back into our routes. We're going to need vendors everywhere. So we're going to want to plant that subscription right here in the configuration. So we can say that we want to globally wait on all of our vendors. So here we're just going to say return Meteor.subcribe to vendors. Now if we go back and refresh the page, we're going to see something interesting. That is our loading functionality, and it is waiting on a subscription, and we have a problem. That subscription doesn't exist, so that loading page is going to sit there forever until it bonks out, so we need to explicitly declare Meteor.publish vendors, and it's going to be all vendors, and then we'll return Vendors.find. Now, it's important in these subscriptions that you use .find, because that returns a cursor, that's what the waitOn expects. There we go, there's our vendors. This page looks all right. We have vendor information, but we don't have any products. Can you guess why? We don't have a subscription for those products yet, so we're going to have to set that up as well. Heading back over to publications, we want to publish all the products for a given vendor. To do this, we are actually going to take in a parameter here, and for that we just simply create a function, and then specify the parameter that we expect to see. In this case, it's going to be the slug of the vendor, that'll be Martian-Armaments, or whatever that you see in the URL, as you can see, martian-armaments. We're going to send that in and we're going to use in a query to return the vendor. So here I just need to say return Products and use a little MongoDB action, saying find, and then vendor.slug is equal to slug. Make sure you put quotes around vendor.slug; if you don't, Mongo is not going to have any idea what you're talking about. Okay, and then here we're just going to put a waitOn specification for that subscription, waitOn, and then Meteor.subscribe, and then products-by-vendor. Heading back over to our page to see if it worked, let's refresh, and yep, there they are. That's great. So you can set up subscriptions based on certain parameters that you need to pass in. So if I change vendors over here, you can see that we now have access to these three products and this one vendor. If we go down to the console just to check this out, look what happens if I put Products.find().fetch(). I only have three products available to me. That's because the only subscription I'm operating on right now is the products by vendor. This might seem a little weird when you're getting used to it, but it's actually fundamental to Meteor and to understanding how publications and subscriptions work. Tightening the UI down as we're doing right here is really good for performance and for security. Okay, let's head over to the product page and, it should come as no surprise, we don't have any product information. We need to set up another publication and subscription for this. So can you guess what we're going to do? Let's set up another publication here, and we'll call it Meteor.publish-- actually, I'll just copy and paste this, that's a little bit easier. And then I'll just say products-by-sku. Take in a sku and then we'll say Products.findOne. I think that'll work. We'll see. And then we'll specify the sku that we're looking for. Now that we have a publication in place, let's go over and set up the subscription, and so I'll go over to our router, and here in products and sku in the productsShow route, I'm going to say waitOn, create a function, and then I'm going to return our Meteor.subscribe, and then products-by-sku. Heading back over to our page and refreshing, we still get something interesting here. The page is just spinning and then nothing. The reason is findOne won't work with a publication. This is something you're just going to have to memorize. Publish expects a cursor, and that is because publish wants reactive data. findOne just returns a single record, and find returns an actual cursor that can be reactive, and there you go. You can see our data. We even saw our loading screen fade away as the page came up. All right, still more of a problem here. Nothing in our cart, because we don't have a cart because we don't have a publication and subscription. So let's do this all again. Have you noticed I like repetition? I find that it's one of the key ways of learning something. You should just keep doing it again and again until it becomes commonplace. So here I'll set up a publication and I'll call it cart. And then it's not going to take any parameters, and I'm simply going to return, and here we go. Carts.find and this is going to accept a userKey, and that'll be the global userKey thing again. I hate that. Notice that I didn't use the method Carts.getCart. The reason for that is because that returns findOne and I need to return a cursor. Instead of putting the subscription down here in the route, however, I'm thinking that later on we're going to want to have access to the cart information all throughout the site, so I'm going to put it up here in the global configuration for the router. To do that, I need to make this an array, and I'm going to say Meteor.subscribe("cart"). This won't work without a userKey, however, so I need to pass along a parameter. So here I would just make sure that I'm accepting a key and the publication. Down below, I'll accept a key in as well. That means up above I'm going to have to pass along that global userKey. This is starting to smell horrible, isn't it? I hate using global variables in this way. Yuck. All right, let's go back and refresh our page, and there is our cart. Our subscription is working as intended. Also notice that if I play with the cart down here in the console, if I do findOne, it's going to return the only cart that I have access to. If I do find.fetch, it will only return that single cart. All right. Let's go back to our receipt page and load it up, but did you notice, we don't have a publication for this, why is it showing up? Do you know why? Well, the simple answer is that I went right around the publication and subscription mechanism when I decided to work with Postgres and use Meteor methods and all that to do this. So there's nothing we need to do for the receipt page. This is all happening outside using NPM and Postgres and Massive. And one last little point of business while we're talking about databases and cleaning things up, I did return to the cart methods there and I switched out the way I was doing saveCart, and what you see here is a method I'm using called updateCart. This simply loops through and makes sure that the only thing a user can do is change a given quantity. Next, I went over to our Meteor settings and I added a database connection string that we can use on the server, and that's what you see here, that is how I'm going to connect to the database, both in development and in production.
Fast Render
So let's think a little bit more about this loading page that we have. If I refresh our receipt page, you can see we have just under a second of loading that needs to happen. That's all the JavaScript that comes down. Once it's all loaded up, I can bit back and forth between the pages without a problem. But sometimes that loading page can be just a little bit obnoxious, especially if here globally you have a lot of subscriptions and you're waiting on a lot of things. This is so typical of Meteor sites and sometimes they call it the Meteor signature. You know you're on a Meteor site when all you see is waiting. So to show you what I mean, let's go over to atmosphere.js, and I'm on a pretty fast connection right now. If I refresh this, the front page here actually loads really quickly. That's what you want. As we start using the functionality of the site, however, that's when things start to slow down. Now right now I'm on a pretty fast connection and it's really early in the morning, so it looks like people aren't using the site very much. But as the site comes under load, and as your connection slows down, you can actually wait up to two or three seconds for a page to load. That can be a little bit maddening for your users, but as long as they're actively doing something like searching, that makes sense. But if they're on your home page, like here at Meteor.com, you want this thing to come up pronto. You do not want any loading thing to happen. For that, there is a package called fast-render, again from Arunoda, and yeah this guy is everywhere. So basically what fast-render does is it takes all the JavaScript that goes into loading a page, generates HTML, and then the front load, that HTML is pumped into this site directly, rather than having to cycle through all the JavaScript. It's actually really dramatic, the speed bump that you can get by using fast-render, and all you've got to do is go through the little demo site, you can see just how fast it is. So, adjusting and manipulating and caressing that loading screen, that'll be a good thing for you to do, especially on customer facing pages that don't have much functionality, like your home page, maybe about, maybe contact. Okay, so let's install fast-render right now, and I do this as you see here, meteor add meteorhacks:fast-render. While this is loading up, we can go back over here to the read me on GitHub and see how to use this thing, and as you can see it's just a Boolean flag, fastRender: true. And you can also read down below on how fast-render actually works in case you have any concerns. One of the things that I hope you see under how fast-render works is that it works on the server and gets the subscription data relevant to the route. Hmm, on the server, something tickling in the back of your head? It is for me. Let's go over and see how we can plug this in now that it is all installed, and I will just go put fastRender: true on our homepage, because you do want this thing to come up quickly. There we go, just set fastRender to true. Seems pretty simple to use. So let's head back over to our site, and it is reloading, and we'll go to the homepage, things are spinning, and if I refresh-- wait a minute. Why is that spinner showing up? Why do I get loading? That's not supposed to be happening. Oh, boy. If we scroll down, we get splashed with errors. Can you guess what the problem is? Looking through the stack trace here, ReferenceError: userKey is not defined. Why would that be? The router works on both the client and the server, it should have access to client data. It's worked so far! Well, the answer is that as I mentioned, fast-render works on the server, and here we are relying on global variables in a bad way and we just got bit by them, and I'm reflecting on this post, on namespacing modules and global variables, and I'm realizing that there's a reason we don't use these things. And that just means that I have one of two choices right now. One, I don't use fast-render; two, I change the way I'm doing the cart. I'm kind of thinking that changing the way I'm doing the cart is probably a better thing, hacking out that global variable. So there's a couple of ways I can handle this. One, I can just have the cart live directly on the client using local storage. That would make me feel better. Two, I write a little more code to get around this problem, which will involve the session and a few more things. I'm not going to go into that now, I'm bringing this up simply to say don't trust the global variable thing as I've been doing, especially when it's information like this, it can get you into trouble. For now I'll just remove fast-render and we'll keep moving forward.
MeteorUp
I feel like our site is tightened up pretty well, now let's go into production, and for this, we use the MeteorUp package. Can you guess who created it? Yep, Arunoda. So what this thing is going to do is it's going to reach out and configure our server, and it's also going to then deploy our site. It's pretty neat. And to install it is pretty simple, but it is not a Meteor package, this is actually an NPM module that you can install globally. So for this, that's what I'm going to do, I'm going to go and install it globally using npm install mup with a -g, that means global. While that's loading up, let's head back over to the read me on GitHub and we'll read up on how to use MeteorUp. It mentions that you can create a directory for your deployment if you want, and then you just need to run mup in it. That's going to create a mup.json and a settings.json. Since I already have a settings.json file, that means that we just need to tweak the mup.json file, which you see here, this is the default that comes with it. We'll go through all those settings in just a second. After that's done, we just run mup setup. It's going to reach out to our server and install software, and then when we're done with that, mup deploy. Sounds pretty simple. Is it going to work? All right, taking a look here in our directory, you can see I already have our settings.json file. If I run mup init, it's going to tell me that a project already exists, so I think this is a bit silly. It's seeing our settings.json and saying I don't want to overwrite that. Anyway, so let's remove it and I will just refill it after I run mup init. Alternatively, I could have just created a mup.json file instead of being so stubborn. Anyway, here is our mup.json file, and this is the default setting that it wants you to fill out. All of these are pretty self-explanatory and it comes commented pretty well. You need to know where your server lives, if it should set up Mongo or set up Node, and you can set that Node version to whatever version you like, although 0.10.36 is recommended. Specify an app name. You can also specify environment variables if you'd like. Notice the app key right there. I've set it to dot. That is the location of your application. So you don't have to put mup.json into your project directory, although that's where I like to put it. You can put it somewhere else if you'd like. All right, let's head over to Digital Ocean, and let's set up a VM , and this is a critical thing. I have found that my deployments don't go so well unless I have at least 2 GB of RAM. You should have that anyway if this is a production site, but I do want to point that out. IN addition, when selecting the version of Ubuntu, I have found that 13.10 x64 works pretty well. The reason I'm telling you all of these details is that I have had problems working with Meteor Up. In fact, getting it to work at all took me a couple hours the very first time. And the reasons were kind of silly. I'll get into them. But the first reasons were that I didn't have compatible versions of Node and I didn't have compatible versions of Ubuntu. So as you've seen me set up here, go with 14.10, make sure you get 2 GB of RAM, and you should be good to go. Okay, so now that my VM is set up, I'm going to take my IP address and head over to DNSimple, and set up an A record here for our brand-new site, and I do that simply by going to shop, and this is for the bigmachine.io domain, and I'll set the time to life to be one minute, and I'll add the record, there we go. All right, now that that is done, I now have a URL shop.bigmachine.io, excellent. Let's SSH into the box and make sure that everything is okay. I don't know why it wouldn't be, but why not? Let's just check and be sure. So, since Digital Ocean already has SSH key here on file up there, whenever I create a new VM and I specify an SSH key for a given machine, it will pop that right onto the machine itself, so I can just log in directly as you see. Great. Well, it looks like everything is order, the machine seems to be functioning properly, from what I could tell in the login screen. Let's run mup setup right now, and I have told mup.json to look at shop.bigmachine.io. I have set the credentials to use my local SSH key, and it's going to reach out, log into the machine, and it's going to, as you see here, install Node, it's installing Phantom, it's going to install Mongo, and rather than sitting here and watch this all go off, let's fast forward a little bit, and as you can see our machine is set up, boom, with all the stuff that we need. Sort of. We are missing one critical thing, and that is I don't have Postgres on the machine just yet. So let's SSH back into the machine and set that up. First thing I'm going to do is I need to make sure that I do an apt-get update, so it's going to go grab all the new packages and sources and so on. The next thing I need to do, and that's critical by the way, because we're installing Postgres 9.4 I need to do that because the JSONB database is only available in 9.4. Okay, it's installed. It looks like Postgres is running. Now let's create a database, and I need to create the Rocket Shop database, so I'll just use the createdb command that comes with Postgres. And I'll specify rocketshop. And that won't work. There is no username root in the database. Postgres is assuming that since I haven't specified which user, that it's going to be my current username, which is root, and it's going to fail. So the only user that exists now in Postgres is the Postgres user, which is kind of like SA or Admin. I can actually switch users over to that, that was installed when I installed Postgres, so if I do su Postgres, that's switch user, I can do that. Then I just have to jump into my own home root and I can run createdb rocketshop. Good. While I'm still in as the Postgres user, let's go to psql and rocketshop, this will open up a SQL prompt. If you don't know what I'm doing, by the way, I have a Postgres course here on Pluralsight and you can check it out and get up to speed. Okay, first thing I'm doing here is I'm creating a new user called rocket with a password takemetomars, and then I'm granting all the database privileges on the rocketshop to my rocket user. All right, that looks pretty good so far. Our work here appears to be finished. So to jump out of psql, I do \q. Jumping out of su I need to type exit, and then jumping out of ssh I type exit again, and I am logged out. Okay, great. So now we are ready for deployment, believe it or not. Our site's ready, our server's ready, let's hit mup deploy and see what happens. And if you get this to work the very first time, congratulations. This is where I hit a brick wall. Over and over and over I saw so many errors, and it's pretty nondescript, the errors that are splashed on the screen. As you are going to see, when you have an error, it's a good idea to stop and take a look at the logs that mup provides. They are far more informative than any screen splash that you'll see from mup. You'll see what I mean in a second. All right, this deployment process can take a few minutes the very first time, especially if you're on a slow connection, so I'll speed it up a little bit. And it looks like our deployment process was a success. If you get an error here, it's because your Meteor site can't start. That is when you need to go look at the logs, which I'll show you how to do in just a second. All right, let's go check out and see if our site launches. I'll go to shop.bigmachine.io, and it looks like it's coming up. It did come up. Wow! All right, so it did deploy. It looks like Add To Cart is working well, it looks like we hit the checkout page just fine. Let's go and look at the page source just so you can see what our production site looks like, and notice that we don't have all of those script tags anymore. They were concatenated and minified and now we just get one big fat dump, because we are in production, and we don't want to have a ton of script tags go live. Okay, I'll hit Terms and Conditions, let's see if it launches up our Stripe checkout, and it does. My goodness, did we do this correctly? Let's hit our test card again using 4 with a bunch of 1s, I'll put in all the test card data here, and then let's pay and see if everything works. Well, Stripe got the information, okay, and we are stuck on redirection. Hmm, that's not so good. Taking a look at the logs, oh boy. It looks like we get a problem invoking process payment. Well, that's not so good. Hmm. That error splash isn't telling me much. Let's go check the logs. If I type in mup logs it's going to go and grab the application logs for our Meteor deployment, and there it is. Error inside the Async.runSync, cannot call method 'save' of undefined. That's an error that's coming straight off of Massive. Massive works by, as I mentioned before, grabbing all the tables, dropping them onto a single instance of an object, and you can access that table call things like save directly. So, if I call db.checkouts.save, it should save all the stuff down to the checkouts table. In our case, it looks like the checkouts table doesn't exist. Well, let's check our connectionString and make sure it's correct. We have the rocket user and takemetomars is the password, and it's pointing at the rocketshop database. That seems okay, we didn't get a connection error. Hmm. Let's sleuth this a little bit more. Let's open up our SalesRepo and use kind of the old guerilla tactic of console.log. This is going to show up in mup logs. I want to see what my database actually looks like. And I'll leave myself a little message here, The DB is, and there we go, I'll just take a look at the db instance that Massive is working against. All right, so we'll redeploy our application, but first I want to call mup logs -f that's going to tail the logs to the screen. I want to watch that live in action as I deploy my site. So let's hit mup deploy once again. Again, this is going to take just a few minutes. And so fast forwarding a little bit to save some time, there we go. If we head back over to our logs, DB is hmm, right there in the middle where I have queryFiles and schemas and executeSqlFile, right there is tables. That should have a value in it. It should be a string value with the name checkouts, and I don't see it, and for some reason, our checkouts table isn't showing up. Hmm. And as I'm seeing these logs, I'm beginning to wonder, did we even install that table? I feel kind of dumb. All right, let's switch users over to Postgres again, and in here let's change directories to our home directory as the Postgres user, and we'll jump into rocketshop one more time. Let's take a look at the schema in rocketshop using \d. No relations found. Nice, good job, Rob. Nicely done! I completely forgot to install the table into the database. Well, I guess if we don't have a table, we can't really save data to it, can we? Okay, so all I need to do here is just take that create statement and paste it in, not forgetting the semicolon, boom, there is our table. Now that I have done that, let's do a \d again to make sure it's there. It is. That's good. We have our checkouts table and our checkouts_id_seq. Exiting out of here all the way back out, exiting out of SSH as well. It says we didn't really change any code. There's no reason to redeploy our site, we can just use the restart command, and that will reach out to our server and just restart our application. This means we can go and look at the logs again and boom, now comes the logs. Wait a minute. There's no table in here. All right, Postgres fans, I think you probably know what's going on. This is such a silly, ridiculous thing. Can you guess what's happening? If we switch back over to psql and do a \d, describe all the tables, you can see that the owner of our table is Postgres. And our rocket user has no right to go and check out the schema. Oh, geez. So I'll run through grant all and checkouts to rocket again. That should do it. Let's flip back over and then restart our app one more time. And then taking a look at the logs, hopefully everything is good. Oh, yes, look at that! I can see checkouts right there, and I think we're in business. Let's jump back over to our site and run through the checkout process one more time to make sure it all works. I can feel it, I think we're getting close. I'm getting excited; I think this is it, I think this is it! Okay, we're back on the checkout page. Everything looks good. I'll accept those terms and conditions and then let's make sure that Stripe checkout pops up, and it does. I'm going to Mars, I can feel it! All right, let's put in our test card here, 4 with all 1s. It's not like I'm nervous or anything, are you nervous? I'm not nervous. Why would I be nervous? Of course it's going to work. Is it going to work? Let's see. Thank you, redirecting, and bingo! We have a live Meteor based ecommerce site that is working. That's exciting.
Hosting Services
I like the idea of having my own server, especially running on Digital Ocean, I love them. However, I really don't like the idea of running MongoDB if I'm going to go into production with it, so for that I would probably use a company like Compose.io. They started around as MongoHQ, and now they have changed their name to Compose.io, and they've reached out to other database platforms, including RethinkDB, Elasticsearch, and so on, but they will manage MongoDB for you and scale it, and I believe they'll do some tuning and you can pay them to come in and test any problems that you might have with it. So if you're going to in production with Meteor, and you know a lot about Mongo, hurray, but if you want someone to look after it for you, I would say that Compose.io is well worth your money. If you want a full turnkey solution, there is Modulus. This is a hosting company that works a little bit like Heroku, you just push your site up there and they deploy it, you can liquid scale it, all those things. Now the one thing that they have that I haven't found in many places is direct support for Meteor. That's pretty neat. You install the Modulus app using NPM install Modulus, and then you use it to deploy your Meteor site, and up it goes to Modulus, and they do all the things that you would expect. They look after your site, make sure it's healthy, and can help you scale it. This is a service that Heroku kind of offers as well. They don't directly support Meteor, but you can run it, and one drawback is that it's not as I said directly supported by Heroku. If you take a look at Heroku Meteor, you can see there's third-party buildpacks, and I guess you can use these, I've never used one, but you can use these to build and deploy your Meteor site if your want. There's a lot of them. As you scroll on down here, you can see Meteor. Frankly, I haven't used Heroku very much. I tried them once or twice with Tech Pub and I didn't have much luck. The last thing I wanted to show you here is the thing that the Modulus people created, which is called the Demeteorizer, I love this. What this does is it basically dismantles your Meteor application and turns it into a regular old Node application, so you could run it anywhere. So the neat thing about this is you just install Demeteorizer and then you tell it to demeteorize your app, and it will. It's sort of a two-step build and deploy process, but all you need is a few clever scripts and off you go, you can run this thing anywhere where Node is supported, including Azure, if you're an Azure fan, or ADWS, et cetera. In this module, we tightened things up by removing autopublish, and we refactored a few things here and there. We dove into pub/sub, latency compensation, and global variables, and we added MeteorUp, or mup, and deployed our site live to Digital Ocean. Then, we checked out different deployment options, such as hosting services, and a neat tool called Demeteorizer.
Final Thoughts and Goodbye
We haven't covered every corner of Meteor in this course. I felt it was more important to give you a sense of what it's like to build and deploy a Meteor application rather than to dive into every single edge concept. Whenever you use a framework with lots of magic and built-in features, Meteor has lots of both, you find yourself confronted with choices. Do you just go with it and do what their framework wants, like the global variable situation, or do you seek different alternatives, like my unwillingness to put any data of consequence in MongoDB? These choices to me are the true measure of the utility of any framework. In other words, how hard is it to just not do what the framework wants you to do? How do you feel about what we've built and how we built it? As you've seen, Meteor is incredibly good at prototyping and beyond. When I was researching and putting the demo together for this course, I was struck, literally struck by just how easy it was to find packages that do things I need, and how simple it was to get a working site together. Sure I encountered bumps along the way, like with the proceed page, that was maddening, but overall I came away feeling fairly positive. How about you? I think Meteor has tremendous promise. I like things the team is doing and paying attention to, but I wish there was a different data story. I do know that various packages allow for support of Postgres, and there's a RethinkDB alternative on the horizon, but the devotion to MongoDB to me, and this is a strong opinion, is a bit discouraging. But we got around it, didn't we? To me checkout data is paramount. Everything else can stay in Mongo for all I care. So when would I use Meteor? That's a good question. If I was working on an internet application, I'd use it without a second thought. For targeted use cases like I mentioned before, maybe a video portal for my old business Tech Pub that needs real time statistics like there's X many people watching this video, or maybe comments, it seems like a natural fit. For the entire Tech Pub site, yeah, I don't know, possibly, but there are few features that I think would make Meteor an easy choice. These aren't a list of demands or complaints by any stretch, but I think having a few key features would make Meteor my default choice in a heartbeat, which it almost is actually. The first is some kind of static page component. Right now everything is driven through the JavaScript engine, which is fine, but if I could marry Jekyll, which is the static site generator that GitHub pages uses, to Meteor, wow. Another idea would be to handle more traditional client server kind of stuff, like routing and templating and so on for when you simply don't need or want a single page application. You could do this I think by allowing access to the underlying web server. Finally, and this is nit-picky honestly, it would be nice to have a better model story. I like keeping data and logic together, and right now, all you get from a Mongo implementation is the back and forth data pump. You can extend this, of course, with packages like collection helpers, but being able to define a model to go with a collection, sort of like Backbone does, that would be really nice. So that's my wish list. I've really enjoyed working with Meteor over the last few months, and I'm really excited to see what's coming in the future. I think it's the perfect choice for targeted applications, and hopefully you're scheming up a few things yourself right now. As for me, that's all I got for you. This course has come to an end. Thank you so much for watching, and as usual, if you have any questions or ideas, you can ping me on Twitter or send me an email using the address onscreen. Thank you so much for watching, I'll see you again soon.
Course author
Rob Conery
Rob Conery co-founded Tekpub and created This Developer's Life. He is an author, speaker, and sometimes a little bit opinionated.
Course info
LevelIntermediate
Rating
(205)
My rating
Duration3h 26m
Released15 Jul 2015
Share course