Plugin architecture system for Expressive Tea framework
npm install @expressive-tea/plugin> ๐ Build once, reuse everywhere - The plugin system that scales with your dreams



> ๐
Versioning: This package uses Calendar Versioning (CalVer) in the format YYYY.MM.MICRO (e.g., 2026.1.0). Learn more โ
---
> [!IMPORTANT]
> ### ๐ Version Compatibility
>
> This package (v2026.1.0) requires @expressive-tea/core >= 2.0.0
>
> - โ
Compatible: @expressive-tea/core@2.x.x and above
> - โ Not compatible: @expressive-tea/core@1.x.x or below
>
> Using Expressive Tea Core v1.x? You'll need @expressive-tea/plugin@1.0.3 (last SemVer version for Core v1.x).
>
> ๐ฆ Expressive Tea Core Reference: This package is designed to work with Expressive Tea Core - the main framework.
---
Ever copy-pasted your database config across 10 projects? ๐ฑ
Ever wished you could share your auth setup between apps? ๐ค
Ever wanted to publish reusable middleware bundles to npm? ๐ฆ
Say hello to the plugin system that makes code reuse actually enjoyable!
- ๐ True Plugin Architecture - Write once, drop into any Expressive Tea app
- ๐ฏ Lifecycle Hooks (Boot Stages) - Control exactly when your code runs
- ๐ Smart Dependencies - Plugins can depend on other plugins (no spaghetti!)
- โก Priority-Based - Fine-tune execution order with simple numbers
- ๐จ Decorator Magic - Just @Stage and you're done
- ๐งช Battle-Tested - 93%+ coverage - we don't ship bugs
- ๐ Self-Documenting - Every API has JSDoc with examples
- ๐ Type-Safe - TypeScript strict mode = fewer runtime surprises
``bashnpm
npm install @expressive-tea/plugin reflect-metadata
๐ป Requirements
- Node.js โฅ 18.0.0 (LTS recommended)
- TypeScript โฅ 5.0.0 (if you're using TypeScript)
- reflect-metadata 0.2.x
- @expressive-tea/commons 2.0.0 (auto-installed as peer dep)
โ๏ธ TypeScript Setup
Enable decorators in your
tsconfig.json:`json
{
"compilerOptions": {
"experimentalDecorators": true, // ๐จ Required for @Stage
"emitDecoratorMetadata": true, // ๐ Required for metadata
"target": "ES2017",
"module": "commonjs"
}
}
`๐ฏ Quick Start (60 seconds)
$3
`typescript
import 'reflect-metadata';
import { Plugin, BOOT_STAGES, Stage } from '@expressive-tea/plugin';
import { Express } from 'express';export class SuperAwesomePlugin extends Plugin {
// Metadata (who are you?)
protected name = 'SuperAwesomePlugin';
protected priority = 100; // Lower = runs first
protected dependencies: string[] = []; // Other plugins you need
// Hook into BOOT_DEPENDENCIES stage
@Stage(BOOT_STAGES.BOOT_DEPENDENCIES)
async connectDatabase(server: Express): Promise {
console.log('๐๏ธ Connecting to database...');
// await db.connect();
}
// Hook into APPLICATION stage
@Stage(BOOT_STAGES.APPLICATION)
setupMiddleware(server: Express): void {
console.log('๐ ๏ธ Adding middleware...');
server.use((req, res, next) => {
console.log(
${req.method} ${req.path});
next();
});
}
}
`$3
`typescript
import { Boot, ServerSettings, Pour } from '@expressive-tea/core';
import { SuperAwesomePlugin } from './plugins/awesome.plugin';@ServerSettings({ port: 3000 })
@Pour(new SuperAwesomePlugin())
class MyApp extends Boot {}
const app = new MyApp();
app.start();
// Output:
// ๐๏ธ Connecting to database...
// ๐ ๏ธ Adding middleware...
// ๐ Server running on http://localhost:3000
`BOOM! ๐ฅ You just created a reusable plugin!
๐ญ Understanding Boot Stages
Think of boot stages as lifecycle hooks - your plugin methods run at specific points during app startup:
| # | Stage | When | Perfect For |
|---|-------|------|-------------|
| 1 |
BOOT_DEPENDENCIES | Very first | Database, Redis, external APIs |
| 2 | APPLICATION | Express setup | Body parsers, CORS, Helmet |
| 3 | CONTROLLERS | Route registration | REST endpoints, GraphQL |
| 4 | APPLICATION_MIDDLEWARES | Before routes | Auth, validation, rate limiting |
| 5 | ROUTE_MIDDLEWARES | Route-specific | Per-route guards |
| 6 | SERVER_SETTINGS | Final setup | Port, SSL, clustering |
| 7 | AFTER_APPLICATION_MIDDLEWARES | Post-routing | Error handlers, 404 |$3
`typescript
export class DatabasePlugin extends Plugin {
protected name = 'DatabasePlugin';
protected priority = 10; // Run early!
@Stage(BOOT_STAGES.BOOT_DEPENDENCIES)
async connectDB(server: Express) {
console.log('1๏ธโฃ Connecting to PostgreSQL...');
// await pool.connect();
} @Stage(BOOT_STAGES.APPLICATION)
addDBMiddleware(server: Express) {
console.log('2๏ธโฃ Adding database middleware...');
// server.use((req, res, next) => {
// req.db = pool;
// next();
// });
}
@Stage(BOOT_STAGES.AFTER_APPLICATION_MIDDLEWARES)
addErrorHandler(server: Express) {
console.log('3๏ธโฃ Adding DB error handler...');
// server.use((err, req, res, next) => {
// if (err.code === 'ECONNREFUSED') {
// res.status(503).json({ error: 'Database unavailable' });
// }
// });
}
}
`๐ Plugin Dependencies (The Cool Part)
Plugins can depend on other plugins - the system ensures they load in the right order!
`typescript
export class SessionPlugin extends Plugin {
protected name = 'SessionPlugin';
protected dependencies = ['DatabasePlugin']; // Needs DB first! @Stage(BOOT_STAGES.APPLICATION)
setupSessions(server: Express) {
console.log('๐พ Setting up sessions (DB already connected!)');
// session logic here
}
}
export class AuthPlugin extends Plugin {
protected name = 'AuthPlugin';
protected dependencies = ['DatabasePlugin', 'SessionPlugin'];
@Stage(BOOT_STAGES.APPLICATION)
setupAuth(server: Express) {
console.log('๐ Setting up auth (DB + sessions ready!)');
// passport.js setup
}
}
// When you use them:
@Pour(new DatabasePlugin())
@Pour(new SessionPlugin())
@Pour(new AuthPlugin())
class MyApp extends Boot {}
// They'll execute in the correct order automatically!
// Output:
// 1. DatabasePlugin methods
// 2. SessionPlugin methods
// 3. AuthPlugin methods
`Missing a dependency? You'll get a helpful
DependencyNotFound error at startup!โก Priority System (Fine-Grained Control)
Within the same boot stage, lower priority = runs first:
`typescript
// Runs FIRST (priority: 10)
export class CorePlugin extends Plugin {
protected priority = 10;
}// Runs SECOND (priority: 100)
export class MiddlewarePlugin extends Plugin {
protected priority = 100;
}
// Runs LAST (priority: 999 - the default)
export class OptionalPlugin extends Plugin {
protected priority = 999;
}
`Pro tip: Use priority 1-100 for critical infrastructure, 100-500 for features, 500+ for optional stuff.
๐จ Advanced Patterns
$3
`typescript
interface DatabaseConfig {
host: string;
port: number;
database: string;
ssl: boolean;
}export class DatabasePlugin extends Plugin {
private config: DatabaseConfig;
constructor(config: DatabaseConfig) {
super();
this.config = config;
}
@Stage(BOOT_STAGES.BOOT_DEPENDENCIES)
async connect(server: Express) {
console.log(
Connecting to ${this.config.host}:${this.config.port}...);
// await createConnection(this.config);
}
}// Usage with environment variables
@Pour(new DatabasePlugin({
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '5432'),
database: process.env.DB_NAME || 'myapp',
ssl: process.env.DB_SSL === 'true'
}))
class MyApp extends Boot {}
`$3
`typescript
export class FullStackPlugin extends Plugin {
protected name = 'FullStackPlugin'; @Stage(BOOT_STAGES.BOOT_DEPENDENCIES)
loadExternalServices(server: Express) {
console.log('๐ก Loading external APIs...');
}
@Stage(BOOT_STAGES.APPLICATION)
setupCORS(server: Express) {
console.log('๐ Configuring CORS...');
// server.use(cors());
}
@Stage(BOOT_STAGES.CONTROLLERS)
registerAPIRoutes(server: Express) {
console.log('๐ฃ๏ธ Registering API routes...');
// Register your routes
}
@Stage(BOOT_STAGES.APPLICATION_MIDDLEWARES)
addAuthMiddleware(server: Express) {
console.log('๐ Adding authentication...');
// server.use(passport.authenticate());
}
@Stage(BOOT_STAGES.AFTER_APPLICATION_MIDDLEWARES)
addErrorHandlers(server: Express) {
console.log('๐จ Adding error handlers...');
// Global error handling
}
}
`$3
`typescript
export class MonitoringPlugin extends Plugin {
protected name = 'MonitoringPlugin';
private enableMetrics: boolean; constructor(options: { enableMetrics?: boolean } = {}) {
super();
this.enableMetrics = options.enableMetrics ?? true;
}
@Stage(BOOT_STAGES.APPLICATION)
setupMonitoring(server: Express) {
if (this.enableMetrics) {
console.log('๐ Enabling metrics endpoint...');
// server.get('/metrics', metricsHandler);
}
if (process.env.NODE_ENV === 'production') {
console.log('๐ Enabling APM monitoring...');
// Initialize APM
}
}
}
`$3
`typescript
// my-awesome-plugin/index.ts
import { Plugin, BOOT_STAGES, Stage } from '@expressive-tea/plugin';
import { Express } from 'express';export class MyAwesomePlugin extends Plugin {
protected name = 'MyAwesomePlugin';
@Stage(BOOT_STAGES.APPLICATION)
setup(server: Express) {
// Your awesome logic
}
}
// Publish to npm as @yourname/awesome-plugin
// Others can use it:
// yarn add @yourname/awesome-plugin
// @Pour(new MyAwesomePlugin())
`๐ Complete API Reference
$3
Extend this to create your plugins:
| Property | Type | Default | What It Does |
|----------|------|---------|-------------|
|
name | string | Class name | Unique plugin identifier |
| priority | number | 999 | Execution order (lower = first) |
| dependencies | string[] | [] | Required plugin names || Method | Returns | What It Does |
|--------|---------|-------------|
|
getRegisteredStage(stage) | Array | Get methods for a boot stage |$3
`typescript
@Stage(stage: BOOT_STAGES, required?: boolean)
`-
stage - When to run this method
- required - (Optional) Throw error if fails (default: false)$3
`typescript
enum BOOT_STAGES {
BOOT_DEPENDENCIES = 'Bootstrap Dependencies',
APPLICATION = 'Bootstrap Application',
CONTROLLERS = 'Bootstrap Controllers',
APPLICATION_MIDDLEWARES = 'Bootstrap Application Middlewares',
ROUTE_MIDDLEWARES = 'Bootstrap Route Middlewares',
SERVER_SETTINGS = 'Bootstrap Server Settings',
AFTER_APPLICATION_MIDDLEWARES = 'After Bootstrap Application Middlewares'
}
`$3
`typescript
import { DependencyNotFound } from '@expressive-tea/plugin';try {
// Plugin initialization
} catch (error) {
if (error instanceof DependencyNotFound) {
console.error(
Missing plugin: ${error.message});
}
}
`๐ ๏ธ Development
`bash
Install dependencies
yarn installBuild
yarn buildTest
yarn testWatch mode
yarn test:watchLint
yarn lintFormat
yarn format
`๐ Project Structure
`
@expressive-tea/plugin/
โโโ src/
โ โโโ classes/
โ โ โโโ Plugin.ts # ๐ฏ Abstract Plugin class
โ โโโ decorators/
โ โ โโโ stage.ts # ๐จ @Stage decorator
โ โโโ helpers/
โ โ โโโ storage-helper.ts # ๐พ WeakMap metadata storage
โ โ โโโ object-helper.ts # ๐ ๏ธ Stage helpers
โ โโโ libs/
โ โ โโโ utilities.ts # โก Native utilities (no lodash!)
โ โโโ exceptions/
โ โ โโโ dependency.ts # โ DependencyNotFound error
โ โโโ constants.ts # ๐ BOOT_STAGES enum
โ โโโ __test__/
โ โ โโโ unit/ # ๐งช Comprehensive tests
โ โโโ index.ts # ๐ฆ Public API
โโโ dist/ # ๐๏ธ Compiled output
โโโ README.md # ๐ You are here!
`๐ก Real-World Plugin Examples
$3
`typescript
import helmet from 'helmet';
import cors from 'cors';
import rateLimit from 'express-rate-limit';export class SecurityPlugin extends Plugin {
protected name = 'SecurityPlugin';
protected priority = 50;
@Stage(BOOT_STAGES.APPLICATION)
enableSecurity(server: Express) {
console.log('๐ก๏ธ Enabling security features...');
server.use(helmet());
server.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(',') }));
server.use(rateLimit({
windowMs: 15 60 1000,
max: 100
}));
}
}
`$3
`typescript
import morgan from 'morgan';
import winston from 'winston';export class LoggingPlugin extends Plugin {
protected name = 'LoggingPlugin';
private logger: winston.Logger;
constructor() {
super();
this.logger = winston.createLogger({
transports: [new winston.transports.Console()]
});
}
@Stage(BOOT_STAGES.APPLICATION)
setupRequestLogging(server: Express) {
console.log('๐ Setting up request logging...');
server.use(morgan('combined'));
}
@Stage(BOOT_STAGES.AFTER_APPLICATION_MIDDLEWARES)
setupErrorLogging(server: Express) {
server.use((err, req, res, next) => {
this.logger.error('Error occurred:', err);
next(err);
});
}
}
`๐ค Contributing
We'd love your plugin ideas and improvements!
1. ๐ด Fork it
2. ๐ฟ Create your feature branch:
git checkout -b feature/epic-plugin
3. โ๏ธ Add tests (we love tests!)
4. โ
Run yarn test - green is good!
5. ๐จ Run yarn lint - make it pretty
6. ๐พ Commit: git commit -m "feat: add epic plugin feature"`Check our Contributing Guide for the full details!
- @expressive-tea/commons - The metadata foundation
- @expressive-tea/core - The full framework
Check CHANGELOG.md for what's cooking in each release!
Our Migration Guide makes it painless!
Stuck? We've got your back:
- ๐ Documentation
- ๐ฌ Gitter Chat - Real-time help
- ๐ GitHub Issues - Report bugs
- ๐ก Discussions - Ask anything
- ๐ง Email - Direct line
Apache-2.0 - Free to use, free to share, free to build amazing things! See LICENSE.
Zero One IT - https://zerooneit.com
- Diego Resendez - Original Author - @chrnx-dev
See all the awesome contributors who helped build this!
---
Made with โ and ๐ต by the Expressive Tea Team
Build plugins. Share plugins. Change the world. ๐