Extending Middleware
The behaviour of the default middlewares can be overwritten or extended by creating a new class which extends from the base middleware class. This section will look into how you can do this for your own project.
Cli
The CLi
middleware uses commander.js
for
parsing the command line input. The following example overrides the
initOptions
method to add additional options for your cli middleware. It also
overrides the getHelp
method to add an example which will be printed wiith
the --help
flag. Lastly it overrides the run
method which parses the cli
input and runs the prrogram based on the input.
// ./middlewares/MyCli.js
import { Cli } from 'pop-api'
export default class MyCli extends Cli {
/**
* @override
*/
constructor(PopApi, {argv, name, version, myCliOption}) {
// Do not pass down the 'argv' key so it does not get parsed by commander.
super(PopApi, {name, version})
// Bind our option to the instance.
this.myCliOption = myCliOption
// Run the Cli middleware.
this.run(PopApi, argv)
}
/**
* @override
*/
initOptions() {
// First initiate the options from the base Cli middleware.
super.initOptions()
// Now you can add your own options.
return this.program
.option('--my-option', 'My awesome option')
}
/**
* @override
*/
getHelp() {
// Get the help message from the base Cli middleware.
const baseHelp = super.getHelp()
// And add your own message for your options.
return baseHelp.concat([
` $ ${this.name} --my-option`
])
}
// Method ot be executed when the `--my-option` flag is set.
runMyOption() {
console.log(`Executing my awesome option: ${this.myCliOption}`)
}
/**
* @override
*/
run(PopApi, argv) {
// Now we parse the options.
this.program.parse(argv)
// Check the use input if your option flag has been filled.
if (this.program.myOption) {
return this.runMyOption()
}
// Run any other input options from the base Cli middleware.
return super.run(PopApi)
}
}
Database
By default the Database
middleware uses
mongoose
to create a Connection to
MongoDB. For this example we will create a MySQL connection with the
mysql
module. It overrides the connect
and disconnect
methods to establish and end a connection.
// ./middlewares/MySqlDatabase.js
import mysql from 'mysql'
import { Database } from 'pop-api'
export default class MySqlDatabase extends Database {
/**
* @override
*/
constructor(PopApi: any, {
database,
hosts = ['localhost'],
dbPort = 3306,
username,
password
}) {
super(PopApi, {
database,
hosts,
dbPort,
username,
password
})
// Bind the connection to the instance to connect and disconnect.
this.connection = mysql.createConnection({
host: this.hosts[0],
user: this.username,
password: this.password,
database: this.database,
port: this.dbPort
})
// Set the database middleware as an instance of MySqlDatabase.
PopApi.database = this
}
/**
* @override
*/
connect() {
return new Promise((resolve, reject) => {
return this.connection
.connect(err => err ? reject(err) : resolve('Connected'))
})
}
/**
* @override
*/
disconnect() {
return new Promise(resolve => this.connection.end())
}
}
Logger
The Logger
middleware uses winston
by default as a logger. Here we will extend the default logger middleware to
use pino
. We override the createLoggerInstance
to create an instance of
pino
and override the consoleFormatter
to use as a formatter function for
pino
.
// ./middlewares/PinoLogger.js
import pino from 'pino'
import { join } from 'path'
import { Logger } from 'pop-api'
import { sprintf } from 'sprintf-js'
export default class PinoLogger extends Logger {
/**
* @override
*/
consoleFormatter(args) {
const level = pino.levels.labels[args.level]
const color = this.getLevelColor(level)
return sprintf(
`\x1b[0m[%s] ${color}%5s:\x1b[0m %2s/%d: \x1b[36m%s\x1b[0m`,
new Date(args.time).toISOString(),
level.toUpperCase(),
this.name,
args.pid,
args.msg
)
}
/**
* @override
*/
createLoggerInstance(suffix, pretty) {
// Let the http logger middleware be handled by the base Logger middleware.
if (suffix === 'http') {
return super.createLoggerInstance(suffix, pretty)
}
const prettyPino = pino.pretty({
// Or don't use a formatter at all.
formatter: this.consoleFormatter.bind(this)
})
prettyPino.pipe(process.stdout)
// Create our logger object.
return pino({
name: `${this.name}-${suffix}`,
safe: true
}, prettyPino)
}
}
Routes
The default web framework used by the Routes
middleware is
express
. For this example we will
extend the Routes
middleware to use
restfy
as the web framework. For
this we will override the preRoutes
method to use middleware for restify
instead of express
.
// ./middlewares/RestifyRoutes.js
import helmet from 'helmet'
import restify from 'restify'
import { Routes } from 'pop-api'
export default class RestifyRoutes extends Routes {
/**
* @override
*/
preRoutes(app) {
// Register the middleware plugins for Restify.
app.use(restify.plugins.bodyParser())
app.use(restify.plugins.queryParser())
app.use(restify.plugins.gzipResponse())
// Use helmet middleware or any other for Restify.
app.use(helmet())
app.use(helmet.contentSecurityPolicy({
directives: {
defaultSrc: ['\'none\'']
}
}))
app.use(this.removeServerHeader)
}
}
Using Custom Middlewares
The 'init' method can take a list of middlewares as a second parameter. This list of middlewares will be used by the PopApi instance. All the middlewares will be initiated with options from the 'init' method, so you can add additional options to your middleware.
// ./index.js
import restify from 'restify'
import { PopApi, HttpServer, utils } from 'pop-api'
import {
MyCli,
MySqlDatabase,
PinoLogger,
RestifyRoutes
} from './middlewares'
import { name, version } from '../package.json'
(async () => {
try {
await PopApi.init({
name,
version,
myCliOption,
...
}, [
MyCli, // Will be initiated with additional 'myCliOption' value.
PinoLogger,
MySqlDatabase,
RestifyRoutes,
HttpServer
])
} catch (err) {
console.error(err)
}
})()