Base node app that utilizes logging, async dependencies, and commander
A powerful Node.js application framework that provides built-in support for components, commands, logging, configuration, and more.
``bash`
npm install @richardpickett/node-app
The easiest way to get started is to copy the example app:
`bashExtract the example app
node-app_example-app.sh
The example app provides a complete working application with the following structure:
`
my-app/
├── src/
│ ├── commands/ # Command definitions
│ │ ├── index.js # Command registration
│ │ ├── globals.js # Global command options
│ │ └── myCommand.js # Individual commands
│ ├── components/ # Custom components
│ │ ├── index.js # Component registration
│ │ └── myComponent.js # Individual components
│ ├── lib/ # Application-specific code
│ │ ├── application.js # Application instance
│ │ └── initialize.js # Initialization logic
│ └── index.js # Application entry point
├── .env # Environment variables
└── package.json
`Basic Usage
$3
`javascript
import application from "./lib/application.js";
import registerComponents from "./components/index.js";
import registerCommands from "./commands/index.js";
import initialize from "./lib/initialize.js";async function run() {
// Register components first
registerComponents(application);
// Then register commands and run the application
return registerCommands(application)
.then(() => initialize())
.then(() => application.run())
.then((result) => process.exit(result ? 0 : 1));
}
run();
`Components
Components are reusable pieces of functionality that can be loaded on demand. They are registered using
registerComponent() and loaded using loadComponents().$3
`javascript
class MyComponent {
constructor(config) {
this.config = config;
} doSomething() {
return "Hello from my component!";
}
}
export default function registerMyComponent(application) {
return application.registerComponent("myComponent", async () => {
// Load dependencies first
return application.loadComponents("config").then(({ config }) => new MyComponent(config));
});
}
`$3
`javascript
import registerMyComponent from "./myComponent.js";export default function registerComponents(application) {
registerMyComponent(application);
}
`$3
`javascript
// Load a single component
application.loadComponents("myComponent").then(({ myComponent }) => {
console.log(myComponent.doSomething());
});// Load multiple components
application.loadComponents(["config", "logger"]).then(({ config, logger }) => {
// Use components here
});
// Alternately, you can load multiple components without the array:
application.loadComponents("config", "logger").then(({ config, logger }) => {
// Use components here
});
// Load components with error handling, if the last parameter is boolean, it indicates an error should not be thrown if a component isn't registered, but instead return false for that component
application
.loadComponents("myComponent", true) // true enables quiet fail mode
.then((components) => {
if (components.myComponent) {
console.log(components.myComponent.doSomething());
}
});
`Commands
Commands are registered using
registerCommand() and can be executed from the command line.$3
`javascript
import application from "../lib/application.js";export default async function registerMyCommand(commander) {
commander
.command("my-command")
.description("Execute my custom command")
.option("-f, --file ", "Path to file")
.action((options) => {
return myCommand(options);
});
}
async function myCommand(options) {
// Load required components
return application.loadComponents(["config", "logger"]).then(({ config, logger }) => {
// Command logic here
logger.info("Executing my command with options:", options);
return true;
});
}
`$3
`javascript
import registerMyCommand from "./myCommand.js";
import registerGlobals from "./globals.js";export default async function registerCommands(application) {
return application.loadComponents("commander").then(({ commander }) => {
// Register global options first
registerGlobals(commander);
// Then register specific commands
registerMyCommand(commander);
});
}
`$3
`javascript
import { Option } from "@richardpickett/node-app";
import application from "../lib/application.js";export default async function registerGlobals(commander) {
return application.loadComponents("config").then(({ config }) => {
// Set default values
config.globalA = "A";
config.globalB = "B";
// Add global options
commander.addOption(new Option("-A, --global-a ", "global a setting").choices(["a", "b"]).default("a", "a"));
commander.option("-B, --global-b ", "global b setting", "b");
// Hook into command execution
commander.hook("preAction", (thisCommand, actionCommand) => {
loadGlobals(thisCommand, config);
});
});
}
function loadGlobals(commander, config) {
// Update config with command line options
config.globalA = commander.opts().globalA;
config.globalB = commander.opts().globalB;
}
`$3
`bash
Run a command
node src/index.js my-commandRun with options
node src/index.js my-command --file ./data.jsonRun with global options
node src/index.js my-command --global-a b --global-b cRun multiple commands
node src/index.js my-command another-command
`Built-in Components
$3
The logger component provides structured logging capabilities:
`javascript
application.loadComponents("logger").then(({ logger }) => {
// Log messages
logger.info("Application started");
logger.error("An error occurred", new Error("Test error"));
logger.debug("Debug information");
});// Set log level
process.env.LOG_LEVEL = "debug"; // Options: error, warn, info, debug
`$3
The config component manages application configuration:
`javascript
application.loadComponents("config").then(({ config }) => {
// Set configuration
config.set("app.name", "My App");
config.set("app.version", "1.0.0"); // Get configuration
const appName = config.get("app.name");
const appVersion = config.get("app.version");
});
`$3
The commander component handles command-line argument parsing:
`javascript
application.loadComponents("commander").then(({ commander }) => {
// Set version
commander.version("1.0.0"); // Add global options
commander.option("-e, --env ", "Set environment", "development");
// Parse arguments
commander.parse(process.argv);
});
`Initializers
Initializers are functions that run during application startup:
`javascript
// Register an initializer
application.registerInitializer("setup", async () => {
// Perform setup tasks
console.log("Setting up application...");
return true; // Return true to indicate success
});// Register multiple initializers
application.registerInitializer("validate", async () => {
// Validate configuration
return true;
});
application.registerInitializer("connect", async () => {
// Connect to database
return true;
});
`Error Handling
The framework provides several built-in error classes:
`javascript
import {
NodeAppBaseError,
NodeAppDuplicateComponentError,
NodeAppDuplicateInitializerError,
NodeAppInitializerFailedError,
NodeAppComponentNotFoundError,
NodeAppInvalidComponentStateError,
} from "@richardpickett/node-app";// Example error handling
application.loadComponents("nonExistentComponent").catch((error) => {
if (error instanceof NodeAppComponentNotFoundError) {
console.error("Component not found:", error.message);
}
});
`Configuration Files
$3
`env
LOG_LEVEL=debug
DB_PASSWORD=secret
API_KEY=abc123
jsonVariables=["A_JSON_ENV_VAR"]
A_JSON_ENV_VAR={"a":1,"b":2}
`Best Practices
1. Component Organization:
- Place component definitions in
src/components/
- Use index.js for component registration
- Keep components focused and single-purpose
- Load dependencies in component registration2. Command Organization:
- Place command definitions in
src/commands/
- Use index.js for command registration
- Group related commands in separate files
- Register global options before specific commands
- Use preAction hooks for global option handling3. Configuration:
- Use
.env` for sensitive data4. Error Handling:
- Use built-in error classes for consistent error handling
- Implement proper error recovery in components
- Log errors with appropriate context
- Return true/false from commands to indicate success/failure
5. Initialization:
- Register components before commands
- Register initializers in order of dependency
- Return true/false to indicate success/failure
- Handle initialization errors gracefully