Overview

The general idea of the pattern is to create flexible routing patterns with routes grouped by required middleware functions.

Once a route receives a request, its piped to a controller method, which decides what to do with the request and is responsible for returning a response.

Next, the controller will dispatch the request object to a store, which handles our CRUD and business logic operations. Having a store to handle the heavy lifting means we can keep our controllers light and our store is free of unnecessary clutter. This decoupling also makes our store highly testable.

To further keep our syntax as light as possible, we take advantage of native ES6 classes. This has no real advantage, other than it makes for high composable code that is easy to read.vs

// meh const dataStore = {}; dataStore.getData = async (a) => { // get some data

}; dataStore.createData = async (a, b) => { // create some data

}; module.exports = dataStore;

vs

// much cleaner! class DataStore { static async getData(a) { //.....get some data

} static async createData(a, b) { //.....create some data

}

} module.exports = DataStore; // or module.exports = new DataStore() if you are not using static methods.

In the first block you will notice I am using the static keyword before the class name. The static keyword simply means I don’t need to instantiate a new instance of the class to use the methods, but since I don’t need a constructor I can skip instantiating and access my class methods directly.

The static keyword defines a static method for a class. Static methods aren't called on instances of the class. Instead, they're called on the class itself. These are often utility functions, such as functions to create or clone objects.

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static

In other languages, such as .NET Core, the usage of statics can cause issues since dependency injection is primarily handled through constructors. Since NodeJS doesn’t use DI like that, we are free to use statics however we wish and it really only comes down to stylistic issues.

Structure

Our directory structure will look roughly like the following. The directory structure also mimics how the pattern flows.

- server.js // Startup logic

-- /routes

--- routes.index.js // index of Routers

--- routes.unauth.js // route definitions for anonymous routes

--- routes.auth.js // route definitions for authenticated routes

- /controllers

-- account.controller.js // pipe request objects to the account store, send response

- /stores

-- account.store.js // receive the request object, handle CRUD.

-- auth.store.js // middleware to authenticate.

Let’s start with out server.js file to kick off listening for requests and serving responses. Nothing fancy here!

Next up is our route index file that is referenced in the previous section.

We export a function that takes the Express app instance as an argument that we can use to create some Express routers that group routes together by middleware (and version if needed).

If you look at app.use() we are giving it a signature of:

Route prefix ( api/v1)

Array of middleware functions that can let the use through based on the presence of authorization headers, their role, etc.

Route definitions contained in another file.

This makes it easy to see which routes get which middleware and keeps things grouped logically. The pattern is highly adaptable if you want to group routes not only be middleware or version, but by resource instead.

Below is an example of authentication middleware referenced in the route index. This article isn’t really about authentication or how middleware works, but I am including it for clarity.

The main take away here is the req and res objects get piped to the middleware and you can act on them as needed to let the request through by calling next() , or choosing to reject the request and return a response before it gets to the route handler. As a matter of convenience, I attached the information from the decoded authorization header to the req object so the user’s identifier is available in the route handler.

Next we setup our unauthorized routes that do not require the user to be authenticated.

The first argument is the route pattern that comes after the root specified in the parent router defined in routes.index.js . The second argument is a controller method (or function) that pipes the req and res arguments to the controller method. We could also specify an array of middleware on a per route basis by adding it as the second argument and pushing the method definition to the 3rd argument.

If you’re not familiar with how you can specify a function to be called that requires arguments and without specifying said arguments, the basic explanation is that as long as the signature of the receiving function matches, the arguments will make it through.

Now, same as above, the difference here is we specified the authentication middleware in routes.index.js meaning these routes are protected.

Let’s get into the good stuff and define our controller. Below we have a class with two static methods. We could go old school and declare all our functions in an object, but the Class method leads to much cleaner code (read: less curly braces).

We use static methods and export the class definition, this means we don’t need an instance of the class. It’s a stylistic preference, we could also initialize an instance of the class and export the instance instead.

module.exports = new AccountController();

The point of the controller is to pull the resources together to assemble a response and send it on it’s way. I tend to keep my controller methods minimal and only responsible for figuring out what to do and sending back a response.

I use the async keyword here, but you can use a promise based approach too.

Finally — we arrive at the store. This is where all the business logic, CRUD and magic happens.

The store is responsible for assembling the “payload” or usable data that will be returned to the requester. We simply pipe in the entire req object so everything important, such as params and the user, are available to us when we need it.

The store can be as complex or simple as we need, the bulk of your code should live in the store. My approach tends to keep the methods small and call sibling methods from within the same class as needed. This keeps things nice, neat and testable.

And that’s it. All done. Any feedback is greatly appreciated.