Skip to main content

Packaging Reusable Code for Micro-services (NodeJS)

· 6 min read
Kinjal Raykarmakar

Cover

Let's say you're building a product which is spread across multiple micro-services. Generally the notion of micro-services is to develop independently, still there might be some parts of code which you'll tend to use in different micro-services as it is.

A naive approach would be writing a piece of code in a particular micro-service and then replicating it into all the others. However this would lead to an inconsistency which is caused be using different versions of the code across different micro-services. In other words, maintaining such a code base would be difficult.

Git Submodules?

Using git submodules, you can set up a directory within your codebase with links up to another git repository. You can write the reusable parts of the code in a standalone git repository and use it as a submodule in all your micr-services.

However, many developer just do not prefer using git submodules. There are various reasons for it, starting from poor development experience to struggles while deploying.

You might want to read them in details here

Packages

Packages are small libraries of code which we install and import in our project to make our lives easier! We do not code everything from scratch. For some stuffs, we prefer the abstraction that a package offers.

A popular package manager and registry among Javascript developers is npm

Public packages help us develop our services. However, we can also create our own packages. We can package our reusable code as a library and use it in any of our micro-services easily.

info

In this post, I would be demonstrating how to package an ExpressJS middleware and use it in a micro-service

Lets get started!

Like Linus Torvalds said:

Talk is cheap. Show me the code.

tip

Not a fan of following through? Head to the GitHub Repository straightway:

KinjuuuWrites/packaging-reusable-code | GitHub

Middlewares (Reusable code part)

Bootstrap a NodeJS project as you want and install the dependencies required. Herein is a basic authentication middleware that verifies a JWT token, decodes it and set's the _id in the request:

authentication.js
const jwt = require("jsonwebtoken");

module.exports = (req, res, next) => {
if (
req.headers.authorization &&
req.headers.authorization.startsWith("Bearer")
) {
try {
const token = req.headers.authorization.split(" ")[1];
const decoded = jwt.verify(token, "myjwtsecretthatnobodyknows");

req.user_id = decoded._id;
next();
} catch (err) {
return res
.status(401)
.json({ error: "Token expired or couldn't be verified!" });
}
} else {
return res.status(401).json({ error: "Unauthorized: No token found" });
}
};

Also adding an index.js file that would help managing multiple middlewares if we plan to extend them!

index.js
const authentication = require("./authentication");

module.exports = {
authentication,
};

You'll see that this package doesn't officially have any entry point or an express server initialized. We didn't even install or use the express here. This is because we're just focusing and developing the middlewares as a standalone package and not the entire server here

Finally the package.json file to complete the project:

package.json
{
"name": "@prc/middlewares",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"pack": "npm pack --pack-destination ../packages"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"jsonwebtoken": "^8.5.1"
}
}

A few things to look out in the package.json file:

  • The name field: See how we prefixed the name with @prc. This is done to scope a package. Generally this is set to the name of your organization. Read in details about scoped packages here
  • The pack script: Used to package the pack the npm library into a tarball. Feel free to set the pack-destination to anywhere in your local system, however take note of it as we'll need that later!
info

For sake of simplicity, I'll use this script to generate a tarball and install from it. In a professional setting, packages are generally published to a registry (Read more about publishing below)!

Run the following command from the project directory to generate the tarball for the package:

npm run pack

You'll see that the tarball is generated as: prc-middlewares-1.0.0.tgz.

ExpressJS server (Micro-service)

Bootstrap a ExpressJS project. Install express and other required dependencies.

Add the middlewares package as dependencies in the package.json file as shown here:

package.json
{
"name": "user",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"@prc/middlewares": "file:../packages/prc-middlewares-2.0.0.tgz",
"express": "^4.18.1",
"jsonwebtoken": "^8.5.1"
}
}

The dependency @prc/middlewares is set as the file path of the packaged tarball file (which was noted in the previous step). This should be a relative path and prefixed with file:

caution

Note: When using a published package, you might need to specify the package version, git repository or package registry details!

Make sure you run install after this:

npm install

Finally the server should look something like this:

server.js
const express = require("express");
const { authentication } = require("@prc/middlewares");
const jwt = require("jsonwebtoken");

const app = express();
app.use(express.json());

app.post("/login", (req, res) => {
const { username, password } = req.body;

if (username === "kinjal" && password === "123456") {
const token = jwt.sign({ _id: 4 }, "myjwtsecretthatnobodyknows");
return res.json({ token });
} else {
return res.status(401).json({ msg: "incorrect credentials!" });
}
});

app.get("/user", authentication, (req, res) => {
return res.json({
msg: "Hello there!",
user_id: req.user_id,
});
});

const PORT = process.env.PORT || 8080;
app.listen(PORT, console.log(`Server is running on port ${PORT} 🚀`));

Note how we require the @prc/middlewares like any other package and use the authentication middleware in the /user route!

Publishing

While developing a bunch of micro-services with multiple developers, packaging libraries locally doesn't solve anything. Instead, developers often prefer versioning and publishing the packages. Packages are generally published to registries as private packages using npm, GitHub or sometimes behind an organization's firewall (like AWS CodeArtifact)

Here's an amazing article to get started with publishing private NPM packages: Creating and publishing private packages | npm Docs

You might also need to work with multiple registries in a single project. Scoped packages come in handy in such situations. To get into details, refer: Loading npm dependencies from multiple registries | Eckher

References: