How to Speed Up Meteor Development with Scaffolding And Automatic Form Generation

Free Course

Build Your First Node.js Website

Node is a powerful tool to get JavaScript on the server. Use Node to build a great website.

Meteor is a web development platform built on top of Node.js and MongoDB. It covers client and server development in JavaScript and fully supports real time behavior and reactivity (automatic update of components when necessary). If you are new to Meteor you can find an introduction here.

Faster Development with Scaffolding

This article is about Meteor scaffolding. With the iron-cli tool scaffolding of Meteor apps becomes super easy. It automatically creates project structure, files and boilerplate code to speed up your development process significantly. In this article we’ll use the iron-cli tool to build a simple Meteor web application from scratch. Furthermore we will use the AutoForms extension to automatically generate HTML forms.

Getting Started

First of all you need to have Meteor installed:

  1. Install on Windows: Download from https://install.meteor.com/windows
  2. Install on Linux / OS X: Use command curl https://install.meteor.com/ | sh

Second we’ll install the scaffolding tool iron-cli which is available on GitHub. Install is done via command line:


npm install -g iron-meteor

After successful install, the iron command is available. You can test it by executing:


iron help

As a result you can see the following output giving you an brief overview of command parameters:

Iron Command

The most important options are:

  • create: Creates a new project structure. The project name needs to be specified as a second parameter, e.g. iron create myprojectname.

  • generate: Generates files and boilerplate code for your project. The generate parameter is followed by colon and the type of component you want to create, e.g. generate:view (can also be shortened to g:view) to create a new view. The name of the component is specified as second parameter, e.g iron g:view myfirstview.

  • run: Runs the app.

Create a Project Structure

As the iron command is available we’ll start by creating a new project:


iron create ironapp01

From the command output you can see that a bunch of files is created and that packages (e.g. the iron:router package) are added to the new project automatically:

New project is created with iron command line tool

Change to the newly created project directory ironapp01 and enter command ls -al to get a first impression of how the project structure looks like:


| .iron                     // iron configuration folder
| app                       // Meteor application folder
| bin                        
| build                     // builds of your applications 
| config                    // environment configuration 
   |----- development/
     |--- env.sh
     |--- settings.json

As you can see, the Meteor application is generated into the subfolder app/. If you take a look into app/ you can find the following structure:


| app
   |----- client
   |----- lib
   |----- packages
   |----- private
   |----- public
   |----- server
 

client/: This is the place where the client code of the Meteor application is stored. The subfolder templates is containing a default home view (home.css, home.html and home.js), a application master layout (master_layout.html and master_layout.js), a template to display a loading message (loading.html) and a template to display a not found error message (not_found.html).


| app
   |--- client/
      |---- head.html
      |---- collections/
      |---- lib/
      |---- stylesheets/             // main stylesheets
        |--- main.css
      |---- templates/               // all views are stored here
        |--- home/                   // home view is created by default
          |-- home.css
          |-- home.html
          |-- home.js
        |--- layouts/               // contains a master layout
          |-- master_layout/
            |- master_layout.html
            |- master_layout.js
        |--- shared/                // templates for displaying loading message / not found message 
          |-- loading/
            |- loading.html
          |-- not_found/
            |- not_found.html

lib/: The lib/ directory contains the application code which is executed both on client and server. By default the scaffolding tool creates the subfolder collections/ and controllers/ within lib/. Within app/lib/controllers you can find the generated file home_controller.js with the following code inside:


HomeController = RouteController.extend({
  layoutTemplate: 'MasterLayout',

  subscriptions: function() {
  },

  action: function() {
this.render('Home');
  }
});

The HomeController contains a property layoutTemplate which is set to ’MasterLayout’ by default. With this setting in place it is ensured that the master layout from file app/client/templates/layouts/master_layout/master_layout.html is used every time the router is calling a HomeController function to render a view template. The subscriptions property of HomeController is a function which is empty by default and used later to subscribe to data collections published by the server. The action function is calling this.render('Home') to output the Home template (stored in app/client/templates/home).

Inside the lib/ directory there are two more files:

  • methods.js: Used to implement client and server methods, empty by default.

  • router.js: Contains the routing configuration.

Take a look into router.js and you’ll find the initial iron:router configuration:

  
Router.configure({
  layoutTemplate: 'MasterLayout',
  loadingTemplate: 'Loading',
  notFoundTemplate: 'NotFound'
});

Router.route('/', {
  name: 'home',
  controller: 'HomeController',
  action: 'action',
  where: 'client'
});

Note: The iron:router package is added to the iron project by default. It is one of the most popular Meteor packages and makes it extremely easy to configure URLs you want to use in your Meteor application. The iron:router is able to handle both, client and server routes at the same time.

First, Router.configure() is used to set the general layout, loading and not found template. Second, the default application route ‘/’ is configured. Route property controller is set to value ‘HomeController‘ to ensure that this controller is used for processing the request. The action property defines the function of the controller which is called to render the browser output.

Note: As seen before, the action method of HomeController is displaying the home view, so all the different parts of the application are wired together by the routing configuration.

public/: This folder is used to store any static assets of your applications like images etc.

server/: Place to store all your Meteor server code. The following structure can be found in server/:


| app
   |--- server/
     |---- collections/      // folder to store store collection code 
     |---- controllers/      
     |---- bootstrap.js      // Meteor startup code can be placed here
     |---- methods.js        // contains all server methods 
     |---- publish.js        // use to publish data collections to the client

Now, that you have a basic understanding of the predefined project structure and the automatically created files we can move on with implementing our sample application. First of all we’ll extend the project by adding some Meteor packages.

Adding, Removing Some Packages

Meteor packages are an easy way to add functionality or external libraries to your project. In the following we’ll use the Meteor package manager to add three packages to our project:

  • aldeed:autoform
  • aldeed:collection2
  • twbs:bootstrap

aldeed:autoform: Easily create forms with automatic insert and update, and automatic reactive validation.

aldeed:collection2: Automatic validation of insert and update operations on the client and server.

twbs:bootstrap: Contains the Bootstrap framework. One of the most popular front-end frameworks for developing responsive, mobile first projects on the web.

Execute the following commands to add the packages:


iron add aldeed:autoform
iron add aldeed:collection2
iron add twbs:bootstrap

Note: Executing iron add in the top level folder of the iron project has the same effect as executing meteor add in the app/ subfolder. In this case the iron command is just acting as a proxy to the meteor command to make it a little bit easier for you (you do not have to change directories and you do not have to remember in which case use meteor and in which case use iron command). In fact you can use the iron command with all parameters which are available for the meteor command.

To ensure packages are installed successfully execute the following command:


iron list

As a result you will see a list of all installed packages in the current project.

Get a list of all Meteor packages installed in project

Note: From the list of packages you can also see that the autopublish and insecure packages are installed. The autopublish package publishes the entire database to all clients. The insecure package allows all database writes by default. Both, automatic publishing and allowing all database writes by default, is not recommendable for a production app. We’ll remove both packages and take back control of data publishing and write access in the next step:


iron remove autopublish
iron remove insecure

Creating a Cars Collection

As the main purpose of the application is to manage a list of cars we’ll start the implementation by creating a cars collection. We’ll do this by using the iron scaffolding command line tool again:


iron g:collection cars

A new file app/lib/collection/cars.js is created. Open this file, delete the Cars.deny rules and return true for the Cars.allow rules (insert, update and remove):


Cars = new Mongo.Collection('cars');

if (Meteor.isServer) {
  Cars.allow({
    insert: function (userId, doc) {
      return true;
    },

    update: function (userId, doc, fieldNames, modifier) {
      return true;
    },

    remove: function (userId, doc) {
      return true;
    }
  });
}

With this configuration in place all actions (insert, update and remove) are allowed for Cars collection on the server.

Attaching a Schema and Publishing Cars Collection

Next step is to define a data schema for our cars collection. Insert the following schema code in file cars.js:


Cars.attachSchema(new SimpleSchema({
  brand: {
    type: String,
    label: "Brand",
    max: 100
  },
  model: {
    type: String,
    label: "Model",
    max: 100
  },
  fueltype: {
    type: String,
    label: "Fuel Type",
    allowedValues: ['Petrol', 'Diesel', 'Hybrid', 'Electric'],
  },
  bodystyle: {
    type: String,
    label: "Body Style",
    allowedValues: ['Convertibles', 'Coupes', 'Hatchbacks', 'Vans', 'Sedans', 'Suvs', 'Trucks', 'Wagons'],
    optional: true
  },
  topspeed: {
    type: Number,
    label: "Top Speed (mph)",
    optional: true
  },
  power: {
    type: Number,
    label: "Power (HP)",
    optional: true
  }
}));

The data schema is the prerequisite for automatic form generation and automatic form validation which we’ll use in a moment.

Publish Collection

As we have removed the autopublish package from our project we now need to publish our data collection explicitly to make data available on the client. Again, we will accomplish this task by using the iron scaffolding command line tool:


iron g:publish cars
 

This command automatically generates the following call of method Meteor.pubish() in file app/server/publish.js:


Meteor.publish('cars', function (/* args */) {
  return Cars.find();
});

As you can see, publishing on server is quite simple. The call of method publish takes as a first parameter a string which contains the name of the publication. The second parameter is a function returning the data which are published.

Creating an Insert Form

As our newly created car collection is empty right now there is nothing to display so far. To enable the user to insert new cars we’ll create an insert form next. With the Cars schema in place and the aldeed:autoforms package installed it’s a simple process. First, create a new template for the insert form:


iron g:template cars/create_car

This will create three new files in app/client/template/cats/create_car:

  • create_car.css
  • create_car.html
  • create_car.js

Open create_car.html and insert the following code:


<template name="CreateCar">
  <h1>Create New Car</h1>
  {{> quickForm collection="Cars" id="insertCarForm" type="insert" buttonContent="Create"}}
</template>

That’s it! No more code needed to get an insert form. Quite simple, isn’t it? The quickForms inclusion is doing all the work for us generating a html form with validation (based on the Cars schema) for us.

Next, let’s add a new controller to our application:


iron g:controller Cars

This command creates the file cars_crontroller.js in app/lib/controllers. Insert a new method create and subscribe to the cars publication:


CarsController = RouteController.extend({
  subscriptions: function () {
    this.subscribe('cars');
  },
  data: function () {
  },
  create: function() {
    this.render('CreateCar', {});
  }
});

This create methods renders the CreateCar template to the browser. Subscriping to the Cars collection is done by calling this.subscribe('cars') in the subscription function.
In the next step we will connect the create method to the new route /cars/create. Open file routes.js and add the following route configuration:


Router.route('/cars/create', {
  name: 'createCar',
  controller: 'CarsController',
  action: 'create',
  where: 'client'
});

With this configuration in place we can start the application by executing the following command:


iron run

Meteor server is running

If you now open the browser and access URL http://localhost:3000/cars/create you can see the form:

Automatically generated form

Without writing HTML form code the output was automatically generated by the AutoForms package. The best thing about it – it’s not only the form’s HTML code, it’s also the logic behind which is automatically generated. You can start right away creating records in the database by filling out the form.

To test if everything is working correctly lets first try to submit the form without filling in a brand value (remember: the brand information is mandatory in our schema):

Error message because of missing value

As you can see an error message is printed out stating that a value for field brand is required. The form is not submitted and further processing is aborted.

Next add some car records by filling in all input fields and submitting the form. If no validation errors occur the form is cleared after successful submission. To check if data records have been created first open the browser console end enter the following command:


Cars.find().fetch()

An array object is returned containing items for every car record created:

Query collection in browser console

The same check can be done on the server. Go to console and enter


meteor mongo

in app/ directory of the project and query the server collection by using the following command:


db.cars.find().pretty()

Query collection in MongoDB console

Again, as a result we get a list of all car records. This shows that the content of the client collection is automatically submitted to the server and stored in the MongoDB database correctly.

Changing the Master Layout

So far our application only consists of one site, displaying a form for creating new car records. Before moving on and adding additional functionality (and templates) we’ll change the MasterLayout to contain some Bootstrap elements to display an application menu. The default MasterLayout template is available in app/client/templates/layouts/master_layout/master_layout.html and contains the following code by default:


<template name="MasterLayout">
  {{> yield}}
</template>

The {{> yield}} inclusion is placed in the template code and a generic element which is replaced by the content of the route-specific template. To make our application look a little bit nicer we add a Bootstrap application menu (responsive) in the header and a footer area at the bottom. Replace the content of master_layout.html with the following template code:


<template name="MasterLayout">
  <nav class="navbar navbar-inverse navbar-fixed-top" role="navigation">
    <div class="container">
      <div class="navbar-header">
        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#navigationbar">
          <span class="sr-only">Toggle navigation</span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
        </button>
        <a class="navbar-brand" href="#">Meteor Car Manager</a>
      </div>
      <div class="collapse navbar-collapse" id="navigationbar">
        <ul class="nav navbar-nav navbar-right">
          <li>{{#linkTo route="home"}}Home{{/linkTo}}</li>
          <li>{{#linkTo route="carsList"}}Cars{{/linkTo}}</li>
          <li>{{#linkTo route="createCar"}}Create New Car{{/linkTo}}</li>
        </ul>
      </div>
    </div>
  </nav>
  <div class="container" style="margin-top:40px">
    {{> yield}}
  </div>
  <nav class="navbar navbar-default navbar-fixed-bottom">
    <div class="container">
      <ul class="nav navbar-nav navbar-right">
        <li>Built with Meteor</li>
      </ul>
    </div>
  </nav>
</template>

Looks much better now:

Master layout with menu and footer

The menu on top contains three links: Home, Cars and Create New Car. The Create New Car link is pointing to the CreateCar template and the Cars link is pointing to the the carsList route which we’ll create later on. To create a link we simply use the {{#linkTo route="[Enter Template Name]"}}[Enter link Text]{/linkTo}} block helper provided by the Iron Router package. The block helper takes the name of the route as parameter and creates an corresponding HTML element in the output.

Displaying a List of Cars

Now, that we have the menu in place we can move on to the next step and create a new template for outputting a list of all cars. Create the template by using scaffolding:


iron g:template cars/list_cars

Next we add a /cars route in routes.js:


Router.route('/cars', {
  name: 'carsList',
  controller: 'CarsController',
  action: 'list',
  where: 'client'
});

The list action method is not existing in the CarsController so far. Lets create this method to output the rendered version of our ListCars template:


list: function() {
  this.render('ListCars', {});
}

If you now click on Cars link in the menu the default template is rendered as you can see in the following screenshot:

New ListCars template is rendered after clicking on link Cars in menu

Next we’ll replace the content of list_cars.html with the following code:


<template name="ListCars">
  <h1>Cars List</h1>
  <table class="table table-hover">
    <thead>
      <tr>
        <th>Brand</th>
        <th>Model</th>
        <th>Fuel Type</th>
        <th>Body Style</th>
        <th>Top Speed</th>
        <th>Power</th>
      </tr>
    </thead>
    <tbody>
      {{#each cars}}
        <tr>
          <td>{{brand}}</td>
          <td>{{model}}</td>
          <td>{{fueltype}}</td>
          <td>{{bodystyle}}</td>
          <td>{{topspeed}}</td>
          <td>{{power}}</td>
        </tr>
      {{/each}}
    </tbody>
  </table>
</template>

That’s the template code which is needed to print out the table of cars. To iterate over all objects in the cars collection a block helper is used: {{#each cars}} ... {{/each}}. The code inside this block helper is rendered for every item in objects cars. But what about cars? This object has not been defined yet, so we need to change that and make the cars object available to the template. Open file list_cars.js and add cars as a Template Helper:


Template.ListCars.helpers({
  cars: function() {
    return Cars.find();
  }
});

With that code in place the cars object is available in the template and the {{#each cars}} ... {{/each}} block will be iterating over all objects in cars. To make sure cars is filled with all data records a function is assigned returning the result of Cars.find().

Now you should see a table with all car records in the browser by accessing URL http://localhost:3000/cars:

List of all car entries in database

Editing and Deleting

To complete our line of business web application we need to add editing and delete capabilities finally. Let’s add one more view template to the project:


iron g:template cars/edit_car

This view will contain an editing form. Again, we’ll use the {{> quickForm ...}} inclusion to generate an editing form on the fly. Insert the following code into file edit_car.html:


<template name="EditCar">
  <h1>Edit</h1>
  {{> quickForm collection="Cars" doc=this id="editCarForm" type="update" buttonContent="Update"}}
</template>

In this case the parameter type is set to value update. This tells quickForm to generate a editing form for updating existing datasets. To access the EditCar template we also need to configure a new route in file app/lib/routes.js:


Router.route('/cars/:_id', {
  name: 'editCar',
  controller: 'CarsController',
  action: 'edit',
  where: 'client'
});

In this case the route string /cars/:_id contains a routing parameter named _id. This parameter takes the identifier of a specific car records as part of the URL. With this information available we can set the data context to this single car records in the CarsController:


data: function () {
  return Cars.findOne({_id: this.params._id});
}

The _id parameter can be accessed via the this.params object. To retrieve the corresponding car entry from the Cars collection the findOne method is called. The resulting data object is returned and assigned to the data property of the controller. Next the edit function is added to the CarsController as this is the function which was assigned to the action property of the editCar routing configuration:


edit: function() {
  this.render('EditCar', {});
}

With the changes made we are now able to access the new edit form in the browser by pointing to URL http://localhost:3000/cars/[ID]. But how to retrieve the identifier of a specific dataset we must include in the URL? You already learned how to query the Cars collection on server by using the Meteor MongoDB Console:


cd app
meteor mongo
db.cars.find().pretty()

The resulting list contains all car records including the _id value:

Use MongoDB Console to retrieve IDs

Just copy one single ID and complete the URL in browser. As a result you get the prefilled editing form:

Form for editing car records

Ok, great so far! But maybe we should make it a little bit easier to access the edit form for a specific record. Let’s add direct links in the ListCars template:


<template name="ListCars">
  <h1>Cars List</h1>
  <table class="table table-hover">
    <thead>
      <tr>
        <th>Brand</th>
        <th>Model</th>
        <th>Fuel Type</th>
        <th>Body Style</th>
        <th>Top Speed</th>
        <th>Power</th>
        <th>Edit</th>
      </tr>
    </thead>
    <tbody>
      {{#each cars}}
        <tr>
          <td>{{brand}}</td>
          <td>{{model}}</td>
          <td>{{fueltype}}</td>
          <td>{{bodystyle}}</td>
          <td>{{topspeed}}</td>
          <td>{{power}}</td>
          <td>{{#linkTo route='editCar'}}<span class="glyphicon glyphicon-edit" aria-hidden="true"></span>{{/linkTo}}</td>
        </tr>
      {{/each}}
    </tbody>
  </table>
</template>

Another column is added to contain a separate link to the edit form for each table row. Again we are using the linkTo block helper. This time linkTo is pointing to the editCar route, so that a valid URL is generated to access the EditCar form template. What is not obvious at first sight, is that the route parameter is set automatically to the current dataset in the generated links. No need to write further code. The result is shown in the following screenshot:

Table now includes links to edit form for every dataset

That was really easy, wasn’t it? Last thing to do is to add delete function so that the user is able to delete existing cars from database. In this case the quickForms generator is no option, as no input form is needed. Instead we just want to add an additional button in the EditCars template enabling the user to delete the current dataset. Let’s see what other packages may help us completing that task quickly. A search for „delete button“ on http://atmospherejs.com shows that there is exactly one package which matches our requirements: aldeed:delete-button.

Search for "delete button" on atmospherejs.com

The package is installed by using the following command:


iron add aldeed:delete-button

After the package is added successfully the new inclusion {{> quickRemoveButton}} becomes available and can be used in the existing EditCar template (edit_car.html):


<template name="EditCar">
  <h1>Edit</h1>
  {{> quickForm collection="Cars" doc=this id="editCarForm" type="update" buttonContent="Update"}}
  <hr>
  {{> quickRemoveButton collection="Cars" _id=this._id beforeRemove=beforeRemove class="btn btn-danger"}}
</template>

As you can see the {{> quickRemoveButton}} inclusions takes four parameters: collection, _id, beforeRemove and class.

  • collection: The collection for which a delete button should be generated.
  • _id: The ID of the dataset which should be deleted by clicking on the button.
  • beforeRemove: A function which should be executed everytime a delete operation is executed.
  • class: CSS classes to be applied.

The beforeRemove function is added because we want to display a confirm message before the record is deleted. The function is implemented as template helper in edit_car.js:


Template.EditCar.helpers({
  beforeRemove: function () {
    return function (collection, id) {
      var doc = collection.findOne(id);
      if (confirm('Really delete car: "' + doc.brand + " " + doc.model + '"?')) {
      this.remove();
      Router.go('carsList');
    }
  };
 }
});

That’s it. The result can be seen in the following screenshot:

Deleting a car record

Live Demo

View Live Demo

Source Code

View Code on GitHub

Conclusion

Meteor is a great platform for web and mobile development which is easy to learn and fun to use. With iron scaffolding Meteor developments becomes even more fun. Generating project structures, components and boilerplate code with just one command gives a boost to your development process.

Hopefully this article has given you a good starting point to dive deeper into Meteor scaffolding with iron-cli and helps you to create great realtime, reactive web apps.

Sebastian

Using and writing about best practices and latest technologies in web design & development is my passion.