Last Updated: February 25, 2016
·
3.097K
· optilude

Meteor code organisation

Some "current thinking" on how best to use Meteor (https://www.meteor.com/):

Packages

Use Meteor 0.9, which includes the package management features previously found in Meteorite.

Some useful packages (install with meteor add)

Code organisation

Here is a simple code layout that fits my way of thinking:

.
├── client/              Client-side-only assets
│   ├── layout.html      Defines an overall page layout for routed pages
│   ├── notfound.html    A default "not found" page for routed requests
│   ├── *.html           Top level pages rendered by the router
│   ├── components/      Shared templates used by top level pages
│   │   ├── *.html
│   ├── css/             Custom stylesheets
│   │   └── *.css
│   ├── js/              Client-side JavaScript logic
│   │   ├── *.js         Template helpers and events for components
│   │   └── startup.js   `Meteor.startup()` and `iron-router` configuration
├── lib/                 Shared library code, loads early
│   └── _namespaces.js   Defines global namespaces
├── models.js            Shared model/collection definitions
├── server/              Server-side-only methods
│   └── *.js

lib/_namespaces.js defines top level namespaces. See https://coderwall.com/p/tk5yug. For example:

/* global Models: true, Collections: true, Schemata: true */

// This file exists so that we can do "use strict" in all other files and
// still have some global namespace variables.

// It is called what it's called and placed where it's placed so that it loads
// as early as possible.

Schemata = {};
Collections = {};

models.js (or possibly a directory of files if there are many models) defines collections and schemata. For example:

/* global Collections, Schemata, SimpleSchema */
"use strict";

Schemata.MyModel = new SimpleSchema({

    ...

});


Collections.MyModels = new Meteor.Collection("MyModels", {
    schema: Schemata.MyModel
});

if(Meteor.isServer) {
    Meteor.publish("models", function() {
        return Collections.MyModels.find({});
    });
} else if(Meteor.isClient) {
    Meteor.subscribe("models");
}

Here is an example of a client/layout.html that uses a simple Bootstrap 3 template:

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta name="description" content="">
    <meta name="author" content="">

    <title>Analysator</title>

    <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
    <!--[if lt IE 9]>
      <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
      <script src="https://oss.maxcdn.com/libs/respond.js/1.4.2/respond.min.js"></script>
    <![endif]-->
</head>

<template name="layout">

    {{> header }}

    <div class="container-fluid">

        <div class="row">
            <div class="col-sm-2 col-md-2 sidebar">
                {{> sidebar }}
            </div>
            <div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
                {{> yield }}
            </div>
        </div>

    </div>

    {{> yield region='footer'}}

</template>

and a client/notfound.html:

<template name="notFound">
    <h1 class="page-header">Not found</h1>
    <p>
        The page you are looking for cannot be found.
    </p>
</template>

The various components like header and sidebar are defined in client/components/*.html as separate templates, with associated logic (e.g. Template.[name].helpers() and Template.[name].events) in client/js/*.js.

Other pages in client/*.html contain alternative pages. These are then navigated to using iron-router. The configuration is in client/js/startup.js. Here's an example that routes to two pages, forces login on anything but the home page, and has a 404 type handler:

/* global Collections */
"use strict";

Meteor.startup(function() {

    // any initialisation here

});

Router.map(function() {

    // if `data` returns undefined/null, redirect to the `notFoundTemplate`.
    Router.onBeforeAction('dataNotFound');
    Router.configure({
        layoutTemplate: 'layout',
        notFoundTemplate: 'notFound'
    });

    // force login
    Router.onBeforeAction(function(pause) {
        var self = this;

        if (!this.ready()) {
            return;
        }

        var userId = Meteor.userId(),
            currentRoute = Router.current();

        // Redirect to home page if user is not logged in
        if(currentRoute && currentRoute.route.name !== 'home' && !userId) {
            pause();
            Router.go('home');
            return;
        }
    });

    // Routes

    this.route('home', {
        path: '/',
        data: {}
    });

    this.route('new', {
        path: '/new-entry',
        template: 'edit-entry',
        yieldTemplates: {
            configurationModal: {to: 'footer'}
        },
        onRun: function() {
            // any preparation, e.g. update Session
        },
        data: function() {
            // return a blank object
            return {foo: null, bar: null};
        }
    });

    this.route('entry', {
        path: '/entry/:_id',
        template: 'edit-entry',
        yieldTemplates: {
            configurationModal: {to: 'footer'}
        },
        onRun: function() {
            // any preparation, e.g. update Session
        },
        data: function() {
            // find the data object
            return Collections.MyModels.findOne(this.params._id);
        }
    });
});

Editor configuration

Have a fresh tip? Share with Coderwall community!

Post
Post a tip
Did you know?

JavaScript on a page runs with the privileges of that origin. One altered library, tag manager, or snippet can read inputs, change what people see, and call your APIs as the user.

Security insights from Cside