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)
}
})()
Manual
Reference
Source
Test
