How you can structure your Express app by using Grogu.js, a lightweight Express boilerplate

OrderStack
5 min readJul 8, 2021

Introduction

If you have read some of our previous articles, you might have noticed that we mostly use NodeJS and Express for our backend developments.

Born in approximately 41 BBY, during the era of the Galactic Republic, this is a work in progress lightweight express framework. A youngling framework.

You can find the Grogu.js repo here.

Importance of structuring your code

Code management is very important, whether you are employing someone to write code for you or you write it yourself. Following a set protocol for writing, code is much recommended as many coders tend to neglect code management before starting writing their application and lose track of their development and eventually the code readability takes a big hit.

Advantages of code management:

  • Better code readability.
  • It gets easier for the next person to write code in your app to understand and proceed forward.
  • You know where your files are/have to be.

Structure your code with Grogu.js

Let’s talk about the structure used in Grogu.js. The structure can be seen in the image below:

The beauty of Grogu.js is that the controllers, services and middlewares are all independent and defined in different directories.

Let’s start by explaining the different folders in Grogu.js with code examples.

Controllers

The quickest way to get started writing controller files is to just create .js files inside /controllers.

A controller file is a PascalCased file, which exports routes and globalMiddlewares (explanation given below the image).

For example, a User Controller could be created at controllers/User.js file containing:

globalMiddlewares is a list of middleware names defined in /middlewares to be applied to all the routes defined inside User.js
routes is a function that returns a key/value pair of routes and their definition and receives Services and config as dependency arguments for usage inside express request handler functions.

The above example code leads to the creation of route as GET on localhost:<PORT>/User/v1.0/hello and POST on localhost:<PORT>/User/v2.2/hello

The handler key holds the response to the request.

Just like globalMiddlewares , you can specify a localMiddleware as well for a specific service.

The dependency injections, Services and config are global and can be used in any Service, Middleware or Controller.

Note that you can change the versions of the API by changing the version: “” to a string of your choice.

Services

Services are functions that help divide and selectively use the core logic of the app. These service functions can be created by defining .js file inside /services. A service file is a PascalCased file, which exports a function that returns an object with all the functions related to that service. For example, a UserService could be created at services/UserService.js file containing:

module.exports = async function ({ config, Services }) {
return {
iDoSomething: async function () {
return "hello world";
},
};
};

Similar to routes in the controller, the function exported from inside the service file receives config and Services as the dependency so as to enable cross usage and sharing of service functions between files.

These functions defined inside each service file then can be called directly inside as seen in Controllers and Middlewares using the syntax as Services.UserService.iDoSomething

Middlewares

Middlewares are express middlewares to be applied on routes definitions. These middlewares are defined as .js files in /middlewares. For example a middleware named checkSomething could be created as middlewares/checkSomething.js

module.exports = async function (req, res, next, { Services, config }) {
next();
};

Here the express middleware function also has a dependency injection of config and Services for usage inside the middleware function.

Config

This is used for using config variables, constants directly inside Services, Middlewares and Controllers. The root config file can be found at config/conf.js containing:

module.exports = {
aws: {
key: process.env.AWS_KEY,
secret: process.env.AWS_SECRET,
ses: {
from: {
default: "noreply@abc.in>",
},
region: "us-west-2",
},
},
};

and similarly, constants are defined inside config/constants containing:

module.exports = {
DEFAULT_NULL_VALUE: "_NULL_",
};

this config and constants are coupled together and can be used directly using syntax config.aws.key and the constants as config.CONSTANTS.DEFAULT_NULL_VALUE wherever the config dependency is available to us.

Note that the constants in config.CONSTANTS.DEFAULT_NULL_VALUE is in all caps.

Models

This will be a folder with a collection of javascript/JSON files that will describe the schema of tables, indexes. The files reside inside models/ and these files are only for informative purposes only. If for example there is a table called users inside a database called example then there will be a folder called models/example.js containing:

module.exports = {
collections: ["users"],
schema: {
users: { username: "string", password: "string" },
},
indexes: {
users: [
[{ username: 1, _id: 1 }, { unique: true }],
[{ username: 1 }, { unique: true }],
],
},
};

Alternatively, this configuration can be used to initialize any indexes or tables.

HTTP server middlewares

These middlewares are HTTP server level middlewares, these are defined inside config/http.js containing:

let bodyParser = require("body-parser").json({ limit: "50mb" });
let compression = require("compression");
let cors = require("cors");
/* This whitelist can only filter requests from the browser clients */
var whitelist = ["http://localhost:3000", "http://localhost:3000/"];
var corsOptions = {
origin: function (origin, callback) {
// console.log("HTTP Origin given = ", origin)
if (!origin) {
// console.log("origin undefined")
callback(null, true);
} else if (whitelist.indexOf(origin) !== -1) {
callback(null, true);
} else if (origin == null) {
// console.log("origin null")
callback(null, true);
} else if (origin.indexOf("chrome-extension") >= 0) {
callback(null, true);
} else {
console.log("[Not allowed by CORS] but allowed temporarily", origin);
callback(null, true);
// callback("Not allowed by CORS", false)
// callback(new Error("Not allowed by CORS"), false)
}
return;
},
};
let corsMiddle = cors(corsOptions);
module.exports = [bodyParser, compression(), corsMiddle];

Here mainly body-parser, cors and other such HTTP server level middlewares are defined.

We hope that this article would help you to structure your Express app in a better, cleaner way.

If you have any suggestions/additions to this repo, do let us know through our social media handles:

Facebook: OrderStack
Instagram: orderstack
Twitter: @orderstack

--

--

OrderStack

At OrderStack, we bring to you an augmented tech team that sells engineering as a service! We work with start-ups to build bespoke MVPs.