Our Global Presence
Canada
57 Sherway St,
Stoney Creek, ON
L8J 0J3
India
606, Suvas Scala,
S P Ring Road, Nikol,
Ahmedabad 380049
USA
1131 Baycrest Drive,
Wesley Chapel,
FL 33544
If you’re building a minimalist product, but want to add an extra level of customisation or expansion, then plugins are a great way to enable that. They allow each instance to be customised with additional code that anyone can create.
In this tutorial, we will extend a minimal web server with a plugin system, create a plugin, and use that plugin with the server. We’ve already created a very basic web server. We’ll use this as our starter code.
But before we start, what is a plugin? A plugin is an extra piece of code that expands what a program can normally do. Web extensions are a good example of this. They leverage existing APIs, and build new functionality upon them. In certain circumstances, they can also be used to patch bugs and flaws that the main software provider has not yet addressed.
In this tutorial, we’ll be building a request counting plugin, and the system to load and unload that plugin.
Let’s start by creating a fresh Node.js project. I’ll be using NPM in this tutorial, but I’ll add the Yarn commands too.
mkdir my-plugin-app
cd my-plugin-app
npm init -y (yarn init -y)
npm i express (yarn add express)
We won’t go through the process of creating an app for our plugins system. Instead, we’ll use this starter code. A simple server with one endpoint. Create an index.js file and add this starter code.
const express = require('express'); const EventEmitter = require('events'); class App extends EventEmitter { constructor() { super(); this.server = express(); this.server.use(express.json()); } start() { this.server.get('/', (req, res) => { res.send('Hello World!'); }); this.server.listen(8080, () => { console.log('Server started on port 3000') this.emit('start'); }); } stop() { if (this.stopped) return; console.log('Server stopped'); this.emit('stop'); this.stopped = true; process.exit(); } } const app = new App(); app.start(); ["exit", "SIGINT", "SIGUSR1", "SIGUSR2", "SIGTERM", "uncaughtException"].forEach(event => { process.on(event, () => app.stop()); });
To get a sense as to what our plugin system will do, let’s create a little plugin containing everything a plugin will need. The type of plugin system we’ll be implementing has two events. load and unload are the two times we will directly call any code in the plugin. load sets up any extra routes, middleware or anything else that is part of the plugin, and unload tells us to safely stop whatever we’re doing and save any persistent data.
This plugin will set up a middleware to count the number of requests we get. In addition, it will add an API route so we can query the number of requests that have been made so far. The plugin will export two different functions, one for each event.
const fs = require('fs'); let count = 0; function load(app) { try { count = +fs.readFileSync('./counter.txt'); } catch (e) { console.log('counter.txt not found. Starting from 0'); } app.server.use((req, res, next) => { count++; next(); }); app.server.get('/count', (req, res) => { res.send({ count }); }) } // Save request count for next time function unload(app) { fs.writeFileSync('./counter.txt', count); } module.exports = { load, unload };
Our plugin system will all be kept in a separate class from the main app, we’ll put this in a new file plugins.js. The point of this class is to load and unload plugins.
The load function takes a path to a plugin file, and uses the require method to load it during runtime. The loadFromConfig method allows us to load plugins defined in a config file.
const fs = require("fs"); class Plugins { constructor(app) { super(); this.app = app; this.plugins = {}; } async loadFromConfig(path='./plugins.json') { const plugins = JSON.parse(fs.readFileSync(path)).plugins; for (let plugin in plugins) { if (plugins[plugin].enabled) { this.load(plugin); } } } async load(plugin) { const path = plugins[plugin]; try { const module = require(path); this.plugins[plugin] = module; await this.plugins[plugin].load(this.app); console.log(`Loaded plugin: '${plugin}'`); } catch (e) { console.log(`Failed to load '${plugin}'`) this.app.stop(); } } } module.exports = Plugins;
We’ll use a plugins.json file to store the paths to all the plugins we wish to load, then call the loadFromConfig method to load them all at once. Put the plugins.json file in the same directory as your code.
{ "counter": "./counter.js" }
Finally, we’ll create an instance of the plugin in our app. Import the Plugins class, create an instance in the constructor, and call loadFromConfig leaving the path blank (the default is ./plugins.json).
const express = require('express'); const Plugins = require('./plugins'); class App { constructor() { super(); this.plugins = new Plugins(this); this.server = express(); this.server.use(express.json()); } async start() { await this.plugins.load(); this.server.get('/', (req, res) => { res.send('Hello World!'); }); this.server.listen(8080, () => { console.log('Server started on port 3000') }); } stop() { if (this.stopped) return; console.log('Server stopped'); this.stopped = true; process.exit(); } } const app = new App(); app.start(); ["exit", "SIGINT", "SIGUSR1", "SIGUSR2", "SIGTERM", "uncaughtException"].forEach(event => { process.on(event, () => app.stop()); });
We now need to handle the unload method exported from our plugin. And once we do, we need to remove it from the plugins collection. We’ll also include a stop method which will unload all plugins. We’ll use this method later to enable safe shutdowns.
const fs = require("fs"); class Plugins { constructor(app) { super(); this.app = app; this.plugins = {}; } async loadFromConfig(path='./plugins.json') { const plugins = JSON.parse(fs.readFileSync(path)).plugins; for (let plugin in plugins) { if (plugins[plugin].enabled) { this.load(plugin); } } } async load(plugin) { const path = plugins[plugin]; try { const module = require(path); this.plugins[plugin] = module; await this.plugins[plugin].load(this.app); console.log(`Loaded plugin: '${plugin}'`); } catch (e) { console.log(`Failed to load '${plugin}'`) this.app.stop(); } } unload(plugin) { if (this.plugins[plugin]) { this.plugins[plugin].unload(); delete this.plugins[plugin]; console.log(`Unloaded plugin: '${plugin}'`); } } stop() { for (let plugin in this.plugins) { this.unload(plugin); } } } module.exports = Plugins;
To make sure that plugins get a chance to unload when the app closes, we need to call Plugins.stop. In the index.js code, we included a stop method that gets called when the app is killed, and we’ve just added a stop method to the Plugins class. So let’s call the Plugins.stop method when our app stop method is called.
Add the following to the App.stop method.
stop() { if (this.stopped) return; + this.plugins.stop(); console.log('Server stopped'); this.stopped = true; process.exit(); }
For more information and to develop web applications using Node JS, Hire Node Developer from us as we give you a high-quality product by utilizing all the latest tools and advanced technology. E-mail us any clock at – hello@hkinfosoft.com or Skype us: “hkinfosoft”.
To develop your custom web app using Node JS, please visit our technology page.
Content Source:
57 Sherway St,
Stoney Creek, ON
L8J 0J3
606, Suvas Scala,
S P Ring Road, Nikol,
Ahmedabad 380049
1131 Baycrest Drive,
Wesley Chapel,
FL 33544
57 Sherway St,
Stoney Creek, ON
L8J 0J3
606, Suvas Scala,
S P Ring Road, Nikol,
Ahmedabad 380049
1131 Baycrest Drive,
Wesley Chapel,
FL 33544
© 2025 — HK Infosoft. All Rights Reserved.
© 2025 — HK Infosoft. All Rights Reserved.
T&C | Privacy Policy | Sitemap