# How Apostrophe modules are structured

# lib/modules: modules in Apostrophe

Apostrophe is a modular content management system. Each meaningful component is broken into its own module, which can then be interacted with or subclassed (extended) by other modules in the system. Under the hood, modules are powered by moog (opens new window) and moog-require (opens new window), but you don't have to understand that right away to build a great website.

The lib/modules folder of your project is where modules created for your own project live. And it is also where you can "implicitly subclass" (i.e. configure or improve upon) Apostrophe's own modules, whether part of the apostrophe npm module's core or packaged in separate npm modules.

We've already seen two modules that are extended in your test project's lib/modules folder, apostrophe-assets and apostrophe-pages. apostrophe-assets gets some custom LESS CSS (opens new window) files, while apostrophe-pages contains page templates.

Apostrophe modules and npm modules are not the same thing. One npm module might package several Apostrophe modules that are maintained together as a "bundle." You'll see this later when you install the apostrophe-blog npm module.

# How Apostrophe modules are structured

Next, you learn how Apostrophe modules are typically structured. Following these naming conventions will improve your own code.

Some of this material will reference advanced Apostrophe concepts. You may want to read the Apostrophe glossary first and complete the basic and intermediate tutorials.

# index.js

index.js is the entry point. Apostrophe (via the moog-require npm module) knows to start there when loading a module. index.js exports a single object that provides a moog definition (opens new window) for the module.

index.js shouldn't "do anything now" other than using require to pull in needed libraries. All the action happens later when the construct and/or afterConstruct functions are invoked by Apostrophe.

It's best to carry out any initialization, like connecting to APIs or databases, in afterConstruct. This allows time for those subclassing and extending your module to replace and extend methods defined in construct.

TIP

Anything configured in app.js for that module merges with the object exported by index.js. However, by convention we donā€™t put much in app.js for each module, to keep it clean. Anything configured in data/local.js also merges with the object, which is useful because data/local.js is not deployed and can thus vary from server to server.

# Breaking it up

A module has a single purpose, so in that sense modules are very well-factored already. However some modules are rather large, so by convention we break them down into multiple files. Each file exports a function that takes (self, options), so the code can be written the same as if it were inside index.js, and in construct we write:

require('./lib/api.js')(self, options);

... As many times as desired.

# Server-side file naming convention

By convention all of the additional server-side .js files for a module live in a ./lib subdirectory of the module. We recommend you follow this standard.

browser.js is where, by convention, we keep code concerned with pushing assets to the browser, overriding the options passed to the singleton that represents this module in the browser, etc. It is not code executed in the browser. It is code that pushes or otherwise aids and abets browser-side code.

helper.js contains nunjucks helpers. It is typically a big olā€™ call to addHelpers. These become visible in nunjucks as apos.moduleAlias.whatever.

routes.js contains Express routes. That is, it contains calls to self.route, such as:

self.route('get', 'sandwiches', function(req, res) { .... }))

Calls to self.route automatically prefix the URL like this: /modules/module-name

So we wind up with: /modules/module-name/sandwiches

This dovetails nicely with self.api and self.html on the browser side, which are available whenever weā€™re extending apostrophe-modal or apostrophe-context.

TIP

You can also work directly with self.apos.app, which is the Express app object, in order to invoke self.apos.app.get and create a route with a more public-friendly URL.

api.js, by convention, usually contains all of the other methods weā€™re adding to the module. This is a bit underwhelming because some methods are likely to get called by project-level developers and/or other modules, and some are obviously implementation details, called automagically, etc.

implementation.js attempts to improve the readability of api.js by limiting itself to methods in the "implementation detail" category. This doesn't mean these methods can't be overridden, and indeed when extending a module it is quite common to want to change these details. Not all of our core modules currently have an implementation.js file.

# Browser-side file naming convention

always.js, by convention, contains code that should always run in the browser, whether the user is logged in or not. This file may contain executable code as a jQuery DOMready function:

$(function() {
  // You should wait at least for the DOM to be ready, like this
});

Or a few Apostrophe event handlers:

apos.on('ready', function() {
  // The page has been loaded for the first time,
  // or has just been refreshed after a piece was saved
});

user.js contains code that should only wind up in the browser if the user is logged in (or scene: 'user' is in effect for a page type).

user.js usually contains a moog definition for a type with the same name as the module. A single instance of that type gets created when the module code on the server side pushes a browser call to do that (pushBrowserCall). There are a well-developed set of convenience methods for this in apostrophe-module, the base class of all modules. Pieces always push user.js as such a singleton; you do not have to reinvent that wheel when extending pieces.

other js files in the browser, by convention, usually define a single additional moog type (with apos.define) and donā€™t contain any other executable code. For instance, editor.js will define an editor modal; user.js will actually instantiate it when an admin bar button is clicked.

# Relax, Apostrophe pushed that for you

If a moduleā€™s base class, i.e. apostrophe-pieces, pushes a JS or CSS asset, then all copies of it in all subclass modules get pushed too. That is, user.js for apostrophe-pieces and for my-articles both get pushed if they exist. You donā€™t have to call pushAsset again. They are pushed in the right order too, so your extend will find the base class.

Also, if cool-articles extends articles which extends pieces, and you want to do something cool in editor.js by defining some new behavior for cool-articles-editor, thatā€™sā€¦ cool. You donā€™t have to suddenly have an editor.js in your articles module that defines articles-editor, unless you feel like it. Apostrophe automatically fills in any gaps in the inheritance tree so you can just define the part you care about. This is a moog feature called mirroring.

Speaking of mirroring, something similar happens on the server side in cursor.js.

apostrophe-doc-type-manager uses self.defineRelatedType to indicate that thereā€™s another moog type related to that module:

  // Define the related type "cursor", so that all of our subclasses
  // automatically have a cursor type too, and it is autoloaded from
  // ./lib/cursor.js if that exists, otherwise given an empty
  // definition.

  self.defineCursor = function() {
    self.defineRelatedType('cursor', {
      stop: 'apostrophe-doc-type-manager'
    });
  };

In a nutshell, this makes it ā€œjust workā€ if you have a cursor.js for your subclass of pieces that adds some extra filters or changes the defaults for how cursors fetching that type of content should behave. And it also ā€œjust worksā€ if you donā€™t.

To be a little more precise, it mirrors the inheritance tree of the module, with ā€œ-cursorā€ added on the end, loading them from ./lib/cursor.js if they exist and filling in the gap with a subclass that doesnā€™t change anything if they donā€™t. The ā€œwalkā€ through the inheritance tree stops with ā€œapostrophe-doc-type-managerā€, so we donā€™t recurse further and define an unwanted ā€œapostrophe-module-cursorā€ type.

As an end result, the find method of apostrophe-doc-type-manager can simply be:

self.find = function(req, criteria, projection) {
  return self.createRelatedType('cursor', { ... various options ... }
  });
};

And the appropriate subclass is automatically used. For instance, in a subclass of apostrophe-pieces called articles, articles-cursor is instantiated.

# Learning more

To learn more, check out the source code of the apostrophe-pieces module, which provides excellent examples of the conventions.